Inspect Module for Introspection#
The inspect module is Python's standard-library toolkit for examining live objects: functions, classes, coroutines, frames, source code, and call signatures. It powers debuggers, documentation generators, test frameworks, and dependency-injection libraries.
How to use this page
Use after Classes and OOP Basics and Dunder or Magic Methods. Pairs with Metaclasses (class construction) and Callable Objects (what inspect recognizes as callable).
At a glance
| Track | Python Advanced → Meta Programming |
| Sections | 11 major topics |
| Outline | Use the right-hand TOC to jump |
Topics: What introspection means in Python · inspect.signature — the workhorse (Python 3.3+) · inspect.getmembers — bulk attribute scan · Classes and the MRO · Source code and stack frames · Coroutines and async inspection · inspect.getclosurevars — closure analysis · inspect.classify_class_attrs · … (+3 more)
- What introspection means in Python
inspect.signature— the workhorse (Python 3.3+)inspect.getmembers— bulk attribute scan- Classes and the MRO
- Source code and stack frames
- Coroutines and async inspection
inspect.getclosurevars— closure analysisinspect.classify_class_attrsinspect.unwrap— following decorator chains- Safe boundaries for introspection
- Practical recipe: CLI from function signature
What introspection means in Python#
Introspection is reading structure and metadata from objects at runtime without executing their business logic.
import inspect
def add(a: int, b: int) -> int:
"""Return the sum."""
return a + b
inspect.isfunction(add) # True
inspect.isbuiltin(print) # True
inspect.ismethod(add) # False — not bound yet
| Question | inspect tool |
|---|---|
| Is it callable? | inspect.isroutine, callable |
| What are the parameters? | inspect.signature |
| Where is it defined? | inspect.getfile, getmodule |
| What is the source? | inspect.getsource |
| Is it a coroutine? | inspect.iscoroutinefunction |
inspect.signature — the workhorse (Python 3.3+)#
import inspect
from typing import Optional
def connect(host: str, port: int = 5432, *, ssl: bool = True) -> None:
...
sig = inspect.signature(connect)
for name, param in sig.parameters.items():
print(name, param.kind, param.default, param.annotation)
Output (conceptually):
| Name | Kind | Default | Annotation |
|---|---|---|---|
host |
POSITIONAL_OR_KEYWORD |
empty | str |
port |
POSITIONAL_OR_KEYWORD |
5432 |
int |
ssl |
KEYWORD_ONLY |
True |
empty |
Parameter.kind values#
| Kind | Meaning |
|---|---|
POSITIONAL_ONLY |
Before / (Python 3.8+) |
POSITIONAL_OR_KEYWORD |
Normal positional or keyword |
KEYWORD_ONLY |
After * or *args |
VAR_POSITIONAL |
*args |
VAR_KEYWORD |
**kwargs |
def demo(a, /, b, *, c, **kwargs): ...
[p.kind for p in inspect.signature(demo).parameters.values()]
Framework pattern — auto-wiring
Web frameworks and CLI libraries bind route paths or flags to function parameters by reading inspect.signature — no duplicate configuration.
def bind_call(func, **kwargs):
sig = inspect.signature(func)
bound = sig.bind_partial(**kwargs)
bound.apply_defaults()
return func(*bound.args, **bound.kwargs)
inspect.getmembers — bulk attribute scan#
import inspect
class Service:
VERSION = "1.0"
def run(self) -> None:
pass
@staticmethod
def helper() -> int:
return 42
# predicate filters what you get back
methods = inspect.getmembers(Service, predicate=inspect.isfunction)
# [('run', <function ...>), ('helper', <function ...>)]
all_members = inspect.getmembers(Service)
| Function | Use |
|---|---|
getmembers(obj) |
All attributes (unless obj.__dir__ customized) |
getmembers(obj, predicate) |
Filtered list of (name, value) pairs |
getmembers_static(obj) |
Python 3.11+ — avoids descriptor execution |
Descriptors can run code
getmembers may trigger @property getters and other descriptors. Use getmembers_static (3.11+) when auditing untrusted objects.
Classes and the MRO#
class A:
def f(self): pass
class B(A):
pass
inspect.getmro(B)
# (<class 'B'>, <class 'A'>, <class 'object'>)
inspect.isclass(B) # True
inspect.ismethod(B.f) # False — unbound function on class
inspect.ismethod(B().f) # True — bound method
Compare with Method Resolution Order for C3 linearization theory.
Source code and stack frames#
import inspect
def outer():
def inner():
return inspect.currentframe()
return inner()
frame = outer()
inspect.getframeinfo(frame)
# FrameInfo(filename='...', lineno=..., function='inner', ...)
inspect.stack() # list of FrameInfo from current frame up
inspect.trace() # stack of current exception (if any)
| API | Returns |
|---|---|
getsource(func) |
Source text as string |
getsourcelines(func) |
(lines, start_lineno) |
getfile(obj) |
Path to defining file |
getmodule(obj) |
Module object or None |
getdoc(obj) |
Cleaned docstring |
Fails for builtins and dynamically compiled code
getsource raises OSError for C builtins, REPL one-liners, and exec()-generated functions.
Coroutines and async inspection#
import asyncio
import inspect
async def fetch():
await asyncio.sleep(0)
inspect.iscoroutinefunction(fetch) # True
inspect.iscoroutine(fetch()) # True — the object returned by calling fetch
def sync(): pass
inspect.iscoroutinefunction(sync) # False
| Function | Detects |
|---|---|
iscoroutinefunction |
async def functions |
iscoroutine |
Coroutine objects |
isawaitable |
Coroutines + objects with __await__ |
isasyncgenfunction |
async def with yield |
inspect.getclosurevars — closure analysis#
def make_multiplier(factor: int):
def multiply(x: int) -> int:
return x * factor
return multiply
times3 = make_multiplier(3)
inspect.getclosurevars(times3)
# ClosureVars(nonlocals={'factor': 3}, globals={...}, builtins={...}, unbound=())
Pairs with Closures and the late-binding loop trap documented there.
inspect.classify_class_attrs#
Groups class attributes by how they are defined:
import inspect
class Base:
class_attr = 1
@property
def name(self) -> str:
return "base"
class Child(Base):
def method(self) -> None:
pass
for attr in inspect.classify_class_attrs(Child):
print(attr.name, attr.kind)
# class_attr class variable
# name property
# method method
kind |
Meaning |
|---|---|
method |
Function in class, unbound |
class method |
@classmethod |
static method |
@staticmethod |
property |
@property |
data / data descriptor |
Plain class attribute or descriptor |
inspect.unwrap — following decorator chains#
from functools import wraps
def logged(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@logged
def compute(x: int) -> int:
return x * 2
inspect.unwrap(compute) is compute # False — wrapper in between
inspect.unwrap(compute).__name__ # 'compute' after peeling wrappers
Essential when decorators stack and you need the original function's signature.
Safe boundaries for introspection#
| Scenario | Guidance |
|---|---|
| Logging / debugging | getsource, signature — generally safe |
| Plugin discovery | Whitelist modules; don't import arbitrary paths |
| Auditing user code | Avoid getmembers without getmembers_static |
| Production hot paths | Cache signature results — reflection has cost |
Import side effects
inspect.getmodule may import packages to resolve dotted names. Never introspect untrusted module paths in security-sensitive contexts.
Practical recipe: CLI from function signature#
import inspect
import sys
def greet(name: str, loud: bool = False) -> str:
msg = f"Hello, {name}!"
return msg.upper() if loud else msg
def main(argv: list[str]) -> None:
sig = inspect.signature(greet)
kwargs = {}
for arg, param in zip(argv, sig.parameters.values()):
if param.annotation is bool:
kwargs[param.name] = arg.lower() in ("true", "1", "yes")
else:
kwargs[param.name] = arg
print(greet(**kwargs))
# python script.py Ada true → HELLO, ADA!
Production CLIs use argparse, click, or typer — but all rely on the same introspection primitives under the hood.
Interview traps (quick reference)#
| Trap | What goes wrong | Safe approach |
|---|---|---|
inspect.ismethod on class attribute |
False for function on class |
Call on instance.method or check inspect.isfunction |
getsource on lambda |
OSError |
Use inspect.getsource only on named defs |
Assuming defaults covers all |
Keyword-only defaults live on Parameter.default |
Iterate signature().parameters |
getmembers triggers properties |
Side effects / exceptions | getmembers_static (3.11+) |
unwrap without follow_wrapped=False |
Stops at first __wrapped__ |
Know when to stop unwrapping |
Mental model checklist#
- What does
inspect.signaturereturn and how do you read parameter kinds? - When is
inspect.ismethodtrue vs false? - What is the difference between
getmembersandgetmembers_static? - How does
inspect.unwraphelp with decorated functions? - Why might
getsourcefail on a valid Python function?
What's next#
| Topic | Page |
|---|---|
| Metaclasses | Metaclasses |
| Callable objects | Callable Objects |
| Type hints | Function Annotations and Type Hints |
| Monkey patching | Monkey Patching |
| Unit testing (mock introspection) | Unit Testing |