inspect.getmembers, signature objects, frame introspection, and source retrieval via linecache.
The inspect module exposes structured runtime introspection. It lets Python code examine objects, functions, classes, methods, frames, tracebacks, source files, signatures, annotations, generators, coroutines, and descriptors.
Where sys exposes low-level interpreter state, inspect builds higher-level views on top of that state.
53.1 The Role of inspect
inspect answers questions about live Python objects:
| Question | Example API |
|---|---|
| What kind of object is this? | inspect.isfunction() |
| What parameters does this callable accept? | inspect.signature() |
| Where was this object defined? | inspect.getsourcefile() |
| What source code created this object? | inspect.getsource() |
| What frame is currently executing? | inspect.currentframe() |
| What local variables exist in a frame? | inspect.getargvalues() |
| What is the state of this generator? | inspect.getgeneratorstate() |
The module is heavily used by debuggers, test frameworks, documentation generators, dependency injection frameworks, RPC systems, decorators, serializers, CLI tools, and metaprogramming libraries.
Conceptually:
runtime object
↓
inspect
↓
metadata, source, signature, frame, or state53.2 Introspection vs Reflection
Introspection means examining program structure at runtime.
Reflection means modifying or invoking program structure dynamically.
inspect is mostly introspective. It reads object metadata, but usually does not mutate interpreter structures.
Example:
import inspect
def add(a, b):
return a + b
print(inspect.isfunction(add))
print(inspect.signature(add))
print(inspect.getsource(add))Output shape:
True
(a, b)
def add(a, b):
return a + bThe source result depends on the function being defined in a real source file.
53.3 Object Classification
The simplest part of inspect classifies objects.
Examples:
import inspect
class User:
def save(self):
pass
def build():
pass
print(inspect.isclass(User))
print(inspect.isfunction(build))
print(inspect.ismethod(User().save))Important classification helpers include:
| Function | Meaning |
|---|---|
isclass() | Object is a class |
isfunction() | Object is a Python function |
isbuiltin() | Object is a built-in function or method |
ismethod() | Object is a bound method |
ismodule() | Object is a module |
isgenerator() | Object is a generator object |
iscoroutine() | Object is a coroutine object |
isawaitable() | Object can be awaited |
isframe() | Object is a frame |
istraceback() | Object is a traceback |
These functions usually check type relationships and internal attributes. They are thin classification layers over the object model.
53.4 Functions and Code Objects
A Python function stores a code object in __code__.
Example:
def f(x, y=10):
z = x + y
return z
print(f.__code__)
print(f.__defaults__)
print(f.__globals__)inspect uses this structure to recover information about the function.
A function object contains:
| Attribute | Meaning |
|---|---|
__code__ | Compiled code object |
__defaults__ | Positional default values |
__kwdefaults__ | Keyword-only defaults |
__globals__ | Global namespace |
__closure__ | Closure cells |
__annotations__ | Type annotations |
__dict__ | Custom function attributes |
__module__ | Defining module |
__qualname__ | Qualified name |
The code object contains lower-level compiler output:
| Code object field | Meaning |
|---|---|
co_argcount | Positional argument count |
co_posonlyargcount | Positional-only argument count |
co_kwonlyargcount | Keyword-only argument count |
co_varnames | Local variable names |
co_consts | Constants |
co_names | Referenced global and attribute names |
co_freevars | Free variables |
co_cellvars | Cell variables |
co_filename | Source filename |
co_firstlineno | First source line |
co_flags | Function flags |
co_code | Raw bytecode bytes |
inspect combines function-level and code-object-level metadata into a user-facing view.
53.5 Signatures
inspect.signature() is one of the most important APIs in the module.
Example:
import inspect
def connect(host, port=5432, *, timeout=30, ssl=False):
pass
sig = inspect.signature(connect)
print(sig)Output:
(host, port=5432, *, timeout=30, ssl=False)The result is a Signature object.
A Signature contains ordered Parameter objects.
for name, param in sig.parameters.items():
print(name, param.kind, param.default)Parameter kinds include:
| Kind | Python syntax |
|---|---|
POSITIONAL_ONLY | def f(x, /) |
POSITIONAL_OR_KEYWORD | def f(x) |
VAR_POSITIONAL | def f(*args) |
KEYWORD_ONLY | def f(*, x) |
VAR_KEYWORD | def f(**kwargs) |
This mirrors CPython’s function calling model.
53.6 Binding Arguments
A signature can bind call arguments without actually calling the function.
Example:
import inspect
def f(a, b=2, *, c=3):
pass
sig = inspect.signature(f)
bound = sig.bind(1, c=10)
print(bound.arguments)Output:
{'a': 1, 'c': 10}Defaults are not inserted automatically unless requested:
bound.apply_defaults()
print(bound.arguments)Output:
{'a': 1, 'b': 2, 'c': 10}This is useful for frameworks that need to validate or transform calls:
CLI command dispatch
HTTP endpoint binding
RPC argument validation
dependency injection
decorator wrappers
test fixture injection53.7 Callable Introspection
Not every callable is a plain Python function.
Callable forms include:
def f():
pass
class C:
def __call__(self):
pass
len
C()inspect.signature() handles many callable types:
| Callable | Source of signature |
|---|---|
| Python function | __code__, defaults, annotations |
| Bound method | Function signature minus bound self |
| Class | __call__, __new__, or __init__ |
| Built-in function | __text_signature__ when available |
| Callable object | type(obj).__call__ |
| Decorated function | __wrapped__ chain |
For built-ins and extension functions, there may be no Python code object. CPython may provide a text signature string instead.
Example:
import inspect
print(inspect.signature(len))Output shape:
(obj, /)The slash means the parameter is positional-only.
53.8 Decorators and __wrapped__
Decorators often replace one function with another.
Example:
def log(fn):
def wrapper(*args, **kwargs):
print("calling")
return fn(*args, **kwargs)
return wrapperWithout care, introspection sees wrapper, not the original function.
The standard solution is functools.wraps():
import functools
import inspect
def log(fn):
@functools.wraps(fn)
def wrapper(*args, **kwargs):
print("calling")
return fn(*args, **kwargs)
return wrapper
@log
def add(a, b):
return a + b
print(add.__wrapped__)
print(inspect.signature(add))functools.wraps() sets __wrapped__.
inspect.signature() follows the __wrapped__ chain by default.
This is why well-written decorators preserve signatures, documentation tools work better, and frameworks can still infer parameters.
53.9 Members
inspect.getmembers() returns named attributes of an object.
Example:
import inspect
class User:
kind = "user"
def save(self):
pass
for name, value in inspect.getmembers(User):
if not name.startswith("__"):
print(name, value)getmembers() roughly performs:
walk object attributes
resolve descriptors
sort by name
return list of pairsIt may invoke descriptors. That means it can execute user code indirectly.
Safer alternative:
inspect.getmembers_static(obj)getmembers_static() avoids dynamic descriptor lookup where possible. This matters for tools that inspect hostile or side-effect-heavy objects.
53.10 Descriptors and Static Lookup
Normal attribute access in Python may call descriptor methods:
__get__
__set__
__delete__Example:
class C:
@property
def value(self):
print("property executed")
return 42
obj = C()
print(obj.value)Inspecting obj.value normally executes the property.
inspect.getattr_static() avoids normal dynamic lookup:
import inspect
raw = inspect.getattr_static(obj, "value")
print(raw)This returns the property object rather than calling it.
This feature is important for:
documentation tools
debuggers
security-sensitive inspection
object browsers
framework metadata collection53.11 Source Code Retrieval
inspect.getsource() tries to retrieve source text for an object.
Example:
import inspect
def f(x):
return x + 1
print(inspect.getsource(f))This depends on several pieces of metadata:
object
↓
code object
↓
co_filename
↓
linecache
↓
source linesFor a function, CPython stores the filename and starting line number in the code object:
print(f.__code__.co_filename)
print(f.__code__.co_firstlineno)inspect uses this information to find and slice the original source file.
Source retrieval can fail when code comes from:
interactive shell
eval()
exec()
generated code
deleted files
zip imports without source access
compiled-only modules
native extension modulesIn those cases, inspect.getsource() raises an error.
53.12 Linecache
The linecache module supports source retrieval.
inspect uses it to read source lines by filename.
Conceptually:
filename
↓
linecache
↓
source lines
↓
inspect block extractionlinecache caches file contents and also supports synthetic sources registered by interactive environments.
This is why notebooks, REPLs, and debuggers often integrate with linecache to make inspection work for generated cells.
53.13 Frames
inspect.currentframe() returns the current frame.
Example:
import inspect
frame = inspect.currentframe()
print(frame.f_code.co_name)
print(frame.f_locals)A frame exposes active execution state:
| Attribute | Meaning |
|---|---|
f_code | Executing code object |
f_locals | Local namespace view |
f_globals | Global namespace |
f_builtins | Builtins namespace |
f_back | Caller frame |
f_lineno | Current line number |
f_trace | Trace function |
Frames are powerful and dangerous. Holding references to frames can keep entire call stacks alive.
Example risk:
import inspect
saved = inspect.currentframe()The frame refers to locals. Locals may refer to large objects. The frame also points to the previous frame through f_back.
This can create memory retention bugs.
A safer pattern:
frame = inspect.currentframe()
try:
print(frame.f_code.co_name)
finally:
del frame53.14 Stack Inspection
inspect.stack() returns records for the current call stack.
Example:
import inspect
def a():
b()
def b():
for frameinfo in inspect.stack():
print(frameinfo.function, frameinfo.lineno)
a()Each frame record contains:
| Field | Meaning |
|---|---|
frame | Frame object |
filename | Source filename |
lineno | Line number |
function | Function name |
code_context | Nearby source text |
index | Position in context |
Stack inspection is expensive. It walks frames and often reads source lines.
It should be avoided in hot paths unless absolutely necessary.
53.15 Tracebacks
A traceback is a linked list of execution frames captured during an exception.
Example:
import inspect
try:
1 / 0
except Exception as exc:
tb = exc.__traceback__
print(inspect.istraceback(tb))Traceback nodes contain:
| Attribute | Meaning |
|---|---|
tb_frame | Frame at this stack level |
tb_lineno | Line number |
tb_lasti | Last bytecode instruction offset |
tb_next | Next traceback node |
inspect can classify and traverse traceback objects, but formatting is usually handled by the traceback module.
53.16 Generators
Generators expose execution state.
Example:
import inspect
def gen():
yield 1
yield 2
g = gen()
print(inspect.getgeneratorstate(g))
next(g)
print(inspect.getgeneratorstate(g))Possible states:
| State | Meaning |
|---|---|
GEN_CREATED | Created, not yet started |
GEN_RUNNING | Currently executing |
GEN_SUSPENDED | Paused at yield |
GEN_CLOSED | Finished or closed |
A generator object stores:
| Attribute | Meaning |
|---|---|
gi_code | Code object |
gi_frame | Suspended frame |
gi_running | Running flag |
gi_yieldfrom | Delegated iterator |
Generators are resumable frames packaged as objects.
53.17 Coroutines
Native coroutines are also inspectable.
Example:
import inspect
async def fetch():
return 1
coro = fetch()
print(inspect.iscoroutine(coro))
print(inspect.getcoroutinestate(coro))
coro.close()Coroutine states include:
| State | Meaning |
|---|---|
CORO_CREATED | Created, not yet awaited |
CORO_RUNNING | Currently executing |
CORO_SUSPENDED | Awaiting something |
CORO_CLOSED | Finished or closed |
Coroutine objects are similar to generators but participate in the await protocol.
53.18 Async Generators
Async generators combine async execution with yielded values.
Example:
import inspect
async def agen():
yield 1
obj = agen()
print(inspect.isasyncgen(obj))They have their own object type and execution state. inspect provides helpers to classify them and understand async code structure.
53.19 Classes and MRO Inspection
inspect can examine classes.
Example:
import inspect
class A:
pass
class B(A):
pass
print(inspect.getmro(B))Output:
(<class '__main__.B'>, <class '__main__.A'>, <class 'object'>)The method resolution order is stored by the type object and controls attribute lookup.
inspect exposes this without requiring direct access to CPython’s PyTypeObject.
53.20 Closure Inspection
Closures store references to variables from outer scopes.
Example:
def outer(x):
def inner():
return x
return inner
fn = outer(10)
print(fn.__closure__)Each closure cell contains a referenced object.
inspect.getclosurevars() gives a structured view:
import inspect
vars = inspect.getclosurevars(fn)
print(vars.nonlocals)
print(vars.globals)
print(vars.builtins)
print(vars.unbound)This is useful for understanding how nested functions capture state.
53.21 Annotations
inspect works with function annotations and signatures.
Example:
import inspect
def f(x: int, y: str = "a") -> bool:
return True
sig = inspect.signature(f)
print(sig.return_annotation)
for param in sig.parameters.values():
print(param.name, param.annotation)Annotations may be actual objects or strings, depending on how the module was compiled and which future imports are active.
Modern annotation handling often involves annotationlib or typing utilities, but inspect remains central for callable metadata.
53.22 Internal Dependencies
inspect depends on several lower-level runtime features:
| Runtime feature | Used for |
|---|---|
| Code objects | Source location and function metadata |
| Frames | Stack inspection |
| Tracebacks | Exception inspection |
| Function attributes | Signatures and closures |
| Descriptor protocol | Member lookup |
| Import system | Module and source resolution |
linecache | Source retrieval |
types | Runtime type classification |
So inspect is less a primitive than a composition layer. It turns raw CPython metadata into stable Python APIs.
53.23 Cost Model
inspect can be expensive.
Cheap operations:
isfunction()
isclass()
ismodule()
signature() on cached/simple functionsPotentially expensive operations:
stack()
getsource()
getmembers()
getmembers_static()
signature() on complex decorated callablesExpensive reasons include:
walking frames
reading files
resolving descriptors
following wrapper chains
parsing text signatures
allocating metadata objectsAvoid heavy inspection inside tight loops.
53.24 Common Failure Modes
Inspection can fail or mislead.
| Case | Behavior |
|---|---|
| Built-in function lacks signature metadata | ValueError |
| Source file unavailable | OSError |
| Decorator hides original function | Wrong signature |
Dynamic __getattr__ has side effects | Inspection may execute code |
| Frame retained too long | Memory retention |
| Generated code uses fake filenames | Source lookup may fail |
Robust tools must handle these failures.
Example:
import inspect
try:
source = inspect.getsource(obj)
except (OSError, TypeError):
source = None53.25 Why inspect Matters for CPython Internals
inspect matters because it exposes CPython’s execution structures in a controlled Python-level form.
It gives access to:
code objects
function metadata
frames
tracebacks
signatures
closures
generator state
coroutine state
class hierarchy
source mappingThese are the same concepts used internally by the compiler, interpreter, debugger hooks, import system, exception machinery, and object model.
Understanding inspect makes later internals easier because it connects visible Python objects to the hidden runtime structures beneath them.
53.26 Chapter Summary
The inspect module is CPython’s high-level introspection layer. It builds on objects, functions, code objects, frames, tracebacks, descriptors, closures, source files, and signatures.
Unlike sys, which exposes raw runtime state, inspect organizes that state into reusable tools. It is the module that turns CPython’s internal execution model into something Python programs can query directly.