Skip to content

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