Home Tutorials Standard Library

Standard Library

Python's inspect Module: Introspecting Live Objects at Runtime

Pyford Notes July 6, 2026 7 min read
Key points
  • inspect.signature(func) returns a structured object describing every parameter — name, default, kind (positional, keyword, *args, **kwargs) — without executing the function.
  • getmembers(obj, predicate) lists every attribute of an object, optionally filtered by predicates like inspect.isfunction or inspect.isclass.
  • getsource() retrieves a function or class's original source text, which is how tools like debuggers and some testing frameworks display exact code context.
  • This module is largely a framework-building tool: application code rarely needs it directly, but decorators, ORMs, and dependency-injection systems lean on it constantly.

inspect.signature(): reading a callable's parameters

inspect.signature() returns a Signature object describing a callable's parameters in detail, without calling it:

import inspect

def greet(name, greeting="Hello", *, shout=False):
    ...

sig = inspect.signature(greet)
for name, param in sig.parameters.items():
    print(name, param.kind, param.default)
name POSITIONAL_OR_KEYWORD <class 'inspect._empty'>
greeting POSITIONAL_OR_KEYWORD Hello
shout KEYWORD_ONLY False

Each parameter reports its kind (whether it can be passed positionally, by keyword, or both), its default value if any (inspect.Parameter.empty if there isn't one), and its annotation if the function used type hints. This is exactly the information a decorator needs when it has to forward arguments correctly, validate them against a schema, or auto-generate a CLI or API from a function's signature — reading the raw *args, **kwargs at call time can't tell you what the original function's parameters were actually named.

getmembers() and the is* predicates

getmembers() lists every attribute on an object as (name, value) pairs, and accepts an optional predicate to filter the results:

class Widget:
    def render(self): ...
    def resize(self): ...
    label = "widget"

inspect.getmembers(Widget, inspect.isfunction)
# [('render', <function ...>), ('resize', <function ...>)]

The module ships a family of these predicates — isfunction, ismethod, isclass, ismodule, isgenerator, and more — each answering a narrow, specific question about an object's type that's more precise than a plain isinstance() check would be, since Python's object model draws real distinctions between a plain function, a bound method, and a classmethod that isinstance alone doesn't surface cleanly.

getsource() and getsourcefile()

For objects defined in an actual .py file (not the interactive interpreter or a dynamically built function), inspect can retrieve the original source text:

print(inspect.getsource(greet))
print(inspect.getsourcefile(greet))    # the file path it came from
print(inspect.getsourcelines(greet))    # (lines list, starting line number)

This underlies plenty of developer tooling most people never think of as introspection-based — a debugger showing the exact line a breakpoint sits on, or a testing framework printing the failing assertion's source when a plain traceback wouldn't show enough context. It raises OSError for anything without retrievable source, such as most built-in functions implemented in C, which is worth catching if the calling code needs to handle arbitrary user-supplied callables gracefully.

currentframe() and stack(): where am I being called from

inspect can also look at the call stack itself:

def log_caller():
    frame = inspect.currentframe().f_back
    print(f"called from {frame.f_code.co_name} at line {frame.f_lineno}")

inspect.stack() returns the full call stack as a list of frame records, each carrying the calling function's name, file, and line number. This is genuinely useful for building better error messages or debug logging that reports where a call originated, but it's also comparatively slow — capturing full stack information involves real overhead — so it's a diagnostic tool for development and logging, not something to call on every iteration of a hot loop.

Where this actually gets used

Ordinary application code that just calls functions and reads attributes rarely needs inspect directly. It shows up constantly, though, underneath tools that operate on code rather than just running it: web frameworks that inspect a view function's parameters to inject the right arguments automatically, dependency-injection containers that read constructor signatures to figure out what to instantiate, and the decorators guide's more advanced examples that need to preserve or inspect a wrapped function's original signature rather than replacing it with a generic *args, **kwargs wrapper. Recognising inspect calls in a library's source is usually a sign it's doing exactly this kind of structural, runtime introspection rather than ordinary business logic.