The inspect module provides tools for examining live objects—functions, classes, modules, and stack frames. Essential for debugging, decorators, and metaprogramming.

Function Signatures

import inspect
 
def greet(name: str, greeting: str = "Hello") -> str:
    """Greet someone."""
    return f"{greeting}, {name}!"
 
sig = inspect.signature(greet)
print(sig)  # (name: str, greeting: str = 'Hello') -> str
 
# Examine parameters
for name, param in sig.parameters.items():
    print(f"{name}: default={param.default}, annotation={param.annotation}")
# name: default=<class 'inspect._empty'>, annotation=<class 'str'>
# greeting: default=Hello, annotation=<class 'str'>

Binding Arguments

import inspect
 
def process(a, b, *args, key=None, **kwargs):
    pass
 
sig = inspect.signature(process)
 
# Bind arguments
bound = sig.bind(1, 2, 3, 4, key="value", extra="data")
print(bound.arguments)
# {'a': 1, 'b': 2, 'args': (3, 4), 'key': 'value', 'kwargs': {'extra': 'data'}}
 
# Apply defaults
bound.apply_defaults()
print(bound.arguments)

Get Source Code

import inspect
 
def example_function():
    """Example docstring."""
    x = 1
    return x + 1
 
# Get source code
source = inspect.getsource(example_function)
print(source)
 
# Get just docstring
doc = inspect.getdoc(example_function)
print(doc)  # "Example docstring."
 
# Get source file
file = inspect.getfile(example_function)
print(file)
 
# Get source lines with line numbers
lines, start_line = inspect.getsourcelines(example_function)

Examining Classes

import inspect
 
class MyClass:
    class_var = "hello"
    
    def __init__(self, value):
        self.value = value
    
    def method(self):
        pass
    
    @classmethod
    def class_method(cls):
        pass
    
    @staticmethod
    def static_method():
        pass
 
# Get all members
for name, value in inspect.getmembers(MyClass):
    print(f"{name}: {type(value).__name__}")
 
# Filter by type
methods = inspect.getmembers(MyClass, predicate=inspect.ismethod)
functions = inspect.getmembers(MyClass, predicate=inspect.isfunction)

Type Checking Predicates

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_func())   # 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's frame
    caller = frame.f_back
    print(f"Called by: {caller.f_code.co_name}")
    
    # Get full stack
    for frame_info in inspect.stack():
        print(f"  {frame_info.function} at {frame_info.filename}:{frame_info.lineno}")
 
def outer():
    inner()
 
outer()

FrameInfo Details

import inspect
 
def get_caller_info():
    frame = inspect.currentframe().f_back
    info = inspect.getframeinfo(frame)
    
    return {
        'filename': info.filename,
        'lineno': info.lineno,
        'function': info.function,
        'code_context': info.code_context,
    }
 
def example():
    caller = get_caller_info()
    print(f"Called from {caller['function']} at line {caller['lineno']}")
 
example()

Decorator Introspection

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:")
        for name, value in bound.arguments.items():
            print(f"  {name} = {value!r}")
        
        return func(*args, **kwargs)
    
    return wrapper
 
@log_calls
def greet(name, greeting="Hello"):
    return f"{greeting}, {name}!"
 
greet("Alice")
# Calling greet with:
#   name = 'Alice'
#   greeting = 'Hello'

Dependency Injection

import inspect
 
class Container:
    def __init__(self):
        self._services = {}
    
    def register(self, cls):
        self._services[cls] = cls
    
    def resolve(self, cls):
        if cls in self._services:
            # Get constructor signature
            sig = inspect.signature(cls.__init__)
            
            # Resolve dependencies
            kwargs = {}
            for name, param in sig.parameters.items():
                if name == 'self':
                    continue
                if param.annotation in self._services:
                    kwargs[name] = self.resolve(param.annotation)
            
            return cls(**kwargs)
        raise KeyError(f"Service {cls} not registered")
 
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)
print(type(service.db))  # <class 'Database'>

Getting Module Members

import inspect
import os
 
# Get all functions in a module
functions = inspect.getmembers(os, inspect.isfunction)
print(f"Functions in os: {len(functions)}")
 
# Get all classes
classes = inspect.getmembers(os, inspect.isclass)
 
# Get module from object
module = inspect.getmodule(os.path.join)
print(module.__name__)  # posixpath or ntpath

Async Function Detection

import inspect
import asyncio
 
def sync_func():
    pass
 
async def async_func():
    pass
 
async def async_gen():
    yield 1
 
print(inspect.iscoroutinefunction(sync_func))   # False
print(inspect.iscoroutinefunction(async_func))  # True
print(inspect.isasyncgenfunction(async_gen))    # True

Parameter Kinds

import inspect
 
def func(pos_only, /, pos_or_kw, *args, kw_only, **kwargs):
    pass
 
sig = inspect.signature(func)
for name, param in sig.parameters.items():
    print(f"{name}: {param.kind.name}")
# pos_only: POSITIONAL_ONLY
# pos_or_kw: POSITIONAL_OR_KEYWORD
# args: VAR_POSITIONAL
# kw_only: KEYWORD_ONLY
# kwargs: VAR_KEYWORD

Class Hierarchy

import inspect
 
class A: pass
class B(A): pass
class C(B): pass
 
# Get base classes
print(inspect.getmro(C))  # (C, B, A, object)
 
# Check inheritance
print(issubclass(C, A))  # True

Cleandoc

import inspect
 
def example():
    """
    This is a docstring.
    
        With some indentation.
        
    And more text.
    """
    pass
 
# Clean up docstring formatting
clean = inspect.cleandoc(example.__doc__)
print(clean)
# This is a docstring.
#
#     With some indentation.
#
# And more text.

Practical: Auto-Documentation

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

The inspect module reveals Python's internals at runtime. Use it for debugging tools, documentation generators, decorators, and framework development.

React to this post: