Introspection & Reflection: Runtime Inspection¶
Overview¶
Introspection examines code at runtime: - inspect module: Powerful runtime reflection - dir(), vars(), type(): Basic inspection - Source code access: Get function source - Signature inspection: Analyze function signatures
Basic Introspection¶
dir() and vars()¶
class Example:
class_var = 42
def __init__(self):
self.instance_var = 10
def method(self):
pass
obj = Example()
# List all attributes
print(dir(obj)) # ['__class__', '__init__', ..., 'class_var', 'instance_var', 'method']
# Get attribute dictionary
print(vars(obj)) # {'instance_var': 10}
# Get type
print(type(obj)) # <class '__main__.Example'>
Checking Properties¶
import inspect
def my_function(x, y=10):
"""Example function."""
return x + y
# Check if callable
print(callable(my_function)) # True
print(callable(42)) # False
# Check if function or method
print(inspect.isfunction(my_function)) # True
# Check if class
print(inspect.isclass(Example)) # True
The inspect Module¶
Function Signature¶
import inspect
def train(model, data, epochs=10, learning_rate=0.001):
"""Train a model."""
pass
# Get signature
sig = inspect.signature(train)
print(sig) # (model, data, epochs=10, learning_rate=0.001)
# Inspect parameters
for param_name, param in sig.parameters.items():
print(f"{param_name}: {param.default}")
# Get return annotation
print(sig.return_annotation) # None (no annotation)
Function Source¶
import inspect
def my_function():
"""Get this function's source."""
return 42
# Get source code
source = inspect.getsource(my_function)
print(source)
# def my_function():
# """Get this function's source."""
# return 42
# Get source lines
lines, line_num = inspect.getsourcelines(my_function)
print(f"Function at line {line_num}")
Class Inspection¶
import inspect
class Base:
def base_method(self):
pass
class Derived(Base):
def derived_method(self):
pass
# Get base classes
print(inspect.getmro(Derived)) # (Derived, Base, object)
# Get members
members = inspect.getmembers(Derived, predicate=inspect.ismethod)
for name, method in members:
print(f"{name}: {method}")
# Get source of class
source = inspect.getsource(Derived)
Dynamic Dispatch¶
Method Lookup¶
import inspect
class Model:
def forward(self, x):
return x * 2
def backward(self, grad):
return grad * 2
model = Model()
# Get method dynamically
method_name = "forward"
method = getattr(model, method_name)
# Call it
result = method(5) # 10
# Check if method exists
if hasattr(model, 'forward'):
print("Model has forward method")
Registry Pattern with Introspection¶
import inspect
class Registry:
def __init__(self):
self.handlers = {}
def register(self, name):
"""Decorator to register handler."""
def decorator(func):
self.handlers[name] = func
return func
return decorator
def register_all(self, obj):
"""Auto-register all methods starting with 'handle_'."""
for name, method in inspect.getmembers(obj, predicate=inspect.ismethod):
if name.startswith('handle_'):
event = name[7:] # Remove 'handle_'
self.handlers[event] = method
registry = Registry()
class EventHandler:
def handle_click(self, event):
print(f"Click: {event}")
def handle_scroll(self, event):
print(f"Scroll: {event}")
handler = EventHandler()
registry.register_all(handler)
# Use registry
print(registry.handlers) # {'click': method, 'scroll': method}
Performance Introspection¶
Profiling Information¶
import inspect
def decorated_function(x):
"""Get info about function."""
return x * 2
# Get function name
print(inspect.getfullargspec(decorated_function).args) # ['x']
# Get docstring
print(inspect.getdoc(decorated_function))
# Count lines
lines = inspect.getsourcelines(decorated_function)[0]
print(f"{len(lines)} lines of code")
Real-World ML Example¶
Auto-Configurable Model¶
import inspect
class ModelFactory:
@staticmethod
def create(class_name, **kwargs):
"""Create model from class name."""
# Get class from registry
model_class = globals()[class_name]
# Get signature
sig = inspect.signature(model_class.__init__)
# Filter kwargs to valid parameters
valid_kwargs = {}
for param_name in sig.parameters:
if param_name != 'self' and param_name in kwargs:
valid_kwargs[param_name] = kwargs[param_name]
# Create instance
return model_class(**valid_kwargs)
class MLPModel:
def __init__(self, hidden_size=256, num_layers=3):
self.hidden_size = hidden_size
self.num_layers = num_layers
# Auto-create with kwargs
model = ModelFactory.create('MLPModel', hidden_size=512, num_layers=5, unknown_param=123)
print(model.hidden_size) # 512
print(model.num_layers) # 5
# unknown_param is ignored
Summary: Introspection Tools¶
| Tool | Use Case |
|---|---|
| dir() | List attributes |
| vars() | Get attribute dict |
| type() | Get object type |
| inspect module | Deep reflection |
| getattr/setattr | Dynamic access |
Related Topics¶
- 01 Getattr Setattr & Proxies - Dynamic attribute access
- 02 Execution Model & Compilation - Runtime behavior
- 05 Metaclasses & Advanced Oop - Type introspection