The inspect module lets you examine live Python objects. Essential for debugging, documentation tools, and metaprogramming.

Function Signatures

import inspect
 
def greet(name: str, excited: bool = False) -> str:
    """Say hello to someone."""
    suffix = "!" if excited else "."
    return f"Hello, {name}{suffix}"
 
sig = inspect.signature(greet)
print(sig)  # (name: str, excited: bool = False) -> str
 
# Examine parameters
for name, param in sig.parameters.items():
    print(f"{name}: {param.annotation}, default={param.default}")
# name: <class 'str'>, default=<class 'inspect._empty'>
# excited: <class 'bool'>, default=False

Getting Source Code

import inspect
 
def example():
    x = 1
    return x + 1
 
# Get source code
print(inspect.getsource(example))
# def example():
#     x = 1
#     return x + 1
 
# Get source file and line number
print(inspect.getfile(example))
print(inspect.getsourcelines(example))  # (lines, start_line)

Docstrings

import inspect
 
def calculate(x, y):
    """
    Calculate the sum of two numbers.
    
    Args:
        x: First number
        y: Second number
    
    Returns:
        The sum of x and y
    """
    return x + y
 
print(inspect.getdoc(calculate))
# Calculate the sum of two numbers.
# 
# Args:
#     x: First number
#     y: Second number
# ...

Class Introspection

import inspect
 
class Animal:
    def speak(self):
        pass
 
class Dog(Animal):
    def speak(self):
        return "Woof"
    
    def fetch(self):
        return "Fetching..."
 
# Get all methods
methods = inspect.getmembers(Dog, predicate=inspect.isfunction)
print(methods)  # [('fetch', <function>), ('speak', <function>)]
 
# Get class hierarchy
print(inspect.getmro(Dog))
# (<class 'Dog'>, <class 'Animal'>, <class 'object'>)
 
# Check inheritance
print(inspect.isclass(Dog))  # True
print(inspect.ismethod(Dog().speak))  # True

Type Checking Functions

import inspect
 
def func(): pass
class MyClass: pass
obj = MyClass()
 
inspect.isfunction(func)      # True
inspect.ismethod(obj.method)  # True (bound method)
inspect.isclass(MyClass)      # True
inspect.ismodule(inspect)     # True
inspect.isgenerator(x for x in [])  # True
inspect.iscoroutine(async_fn())     # True
inspect.isasyncgen(async_gen())     # True

Call Stack Inspection

import inspect
 
def inner():
    # Get current frame
    frame = inspect.currentframe()
    print(f"Current function: {frame.f_code.co_name}")
    
    # Get caller info
    caller = inspect.stack()[1]
    print(f"Called by: {caller.function} at line {caller.lineno}")
 
def outer():
    inner()
 
outer()
# Current function: inner
# Called by: outer at line 12

Stack Frames

import inspect
 
def debug_locals():
    """Print local variables of caller."""
    frame = inspect.currentframe().f_back
    print("Caller's locals:", frame.f_locals)
 
def example():
    x = 10
    y = "hello"
    debug_locals()
 
example()
# Caller's locals: {'x': 10, 'y': 'hello'}

Practical Examples

Decorator with Signature Preservation

import inspect
from functools import wraps
 
def log_calls(func):
    sig = inspect.signature(func)
    
    @wraps(func)
    def wrapper(*args, **kwargs):
        bound = sig.bind(*args, **kwargs)
        bound.apply_defaults()
        print(f"Calling {func.__name__} with {dict(bound.arguments)}")
        return func(*args, **kwargs)
    
    return wrapper
 
@log_calls
def add(a, b, c=0):
    return a + b + c
 
add(1, 2)
# Calling add with {'a': 1, 'b': 2, 'c': 0}

Auto-Documentation

import inspect
 
def document_class(cls):
    """Generate documentation for a class."""
    doc = [f"# {cls.__name__}\n"]
    
    if cls.__doc__:
        doc.append(f"{inspect.getdoc(cls)}\n")
    
    doc.append("\n## Methods\n")
    
    for name, method in inspect.getmembers(cls, predicate=inspect.isfunction):
        if not name.startswith('_'):
            sig = inspect.signature(method)
            doc.append(f"### {name}{sig}\n")
            if method.__doc__:
                doc.append(f"{inspect.getdoc(method)}\n")
    
    return "\n".join(doc)

Dependency Injection

import inspect
 
class Container:
    def __init__(self):
        self._services = {}
    
    def register(self, cls):
        self._services[cls] = cls
    
    def resolve(self, cls):
        sig = inspect.signature(cls)
        deps = {}
        
        for name, param in sig.parameters.items():
            if param.annotation in self._services:
                deps[name] = self.resolve(param.annotation)
        
        return cls(**deps)
 
# Usage
class Database:
    pass
 
class UserService:
    def __init__(self, db: Database):
        self.db = db
 
container = Container()
container.register(Database)
container.register(UserService)
 
service = container.resolve(UserService)
# service.db is automatically injected

Validation Decorator

import inspect
 
def validate_types(func):
    sig = inspect.signature(func)
    
    def wrapper(*args, **kwargs):
        bound = sig.bind(*args, **kwargs)
        
        for name, value in bound.arguments.items():
            param = sig.parameters[name]
            if param.annotation != inspect.Parameter.empty:
                if not isinstance(value, param.annotation):
                    raise TypeError(
                        f"{name} must be {param.annotation.__name__}, "
                        f"got {type(value).__name__}"
                    )
        
        return func(*args, **kwargs)
    
    return wrapper
 
@validate_types
def greet(name: str, times: int):
    return name * times
 
greet("hi", 3)    # "hihihi"
greet("hi", "3")  # TypeError

Quick Reference

import inspect
 
# Signatures
inspect.signature(func)
inspect.signature(func).parameters
inspect.signature(func).return_annotation
 
# Source code
inspect.getsource(obj)
inspect.getsourcefile(obj)
inspect.getsourcelines(obj)
inspect.getdoc(obj)
 
# Type checks
inspect.isfunction(obj)
inspect.ismethod(obj)
inspect.isclass(obj)
inspect.ismodule(obj)
inspect.isgenerator(obj)
inspect.iscoroutine(obj)
 
# Class info
inspect.getmembers(cls)
inspect.getmro(cls)
 
# Stack
inspect.currentframe()
inspect.stack()
inspect.getframeinfo(frame)
FunctionReturns
signature(fn)Signature object
getsource(obj)Source code string
getmembers(obj)List of (name, value) pairs
getmro(cls)Method resolution order
stack()List of FrameInfo
currentframe()Current frame object

inspect is the foundation for debugging tools, documentation generators, and frameworks. Use it to understand code at runtime.

React to this post: