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=FalseGetting 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)) # TrueType 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()) # TrueCall 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 12Stack 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 injectedValidation 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") # TypeErrorQuick 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)| Function | Returns |
|---|---|
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: