Python’s inspect module examines live objects: classes, functions, frames, and source code. import inspect. getmembers: inspect.getmembers(obj) → list of (name, value) pairs. getmembers_static: inspect.getmembers_static(obj) (3.11+) — no descriptors triggered. isfunction: inspect.isfunction(obj). ismethod: inspect.ismethod(obj). isclass: inspect.isclass(obj). ismodule: inspect.ismodule(obj). iscoroutinefunction: inspect.iscoroutinefunction(fn). isgeneratorfunction: inspect.isgeneratorfunction(fn). isasyncgenfunction: inspect.isasyncgenfunction(fn). isbuiltin: inspect.isbuiltin(obj). signature: sig = inspect.signature(fn) → Signature; sig.parameters → OrderedDict[str, Parameter]. Parameter: .name, .default, .annotation, .kind (POSITIONAL_ONLY, POSITIONAL_OR_KEYWORD, VAR_POSITIONAL, KEYWORD_ONLY, VAR_KEYWORD). bind: ba = sig.bind(1, x=2) → BoundArguments; .arguments, .apply_defaults(). getsource: inspect.getsource(obj) → source string. getsourcelines: (lines, lineno). getsourcefile: source file path. getdoc: inspect.getdoc(obj) → cleaned docstring or None. cleandoc: inspect.cleandoc(raw). stack: inspect.stack() → list of FrameInfo; [0] is current frame. currentframe: inspect.currentframe() → frame object. getframeinfo: inspect.getframeinfo(frame) → Traceback(filename, lineno, function, code_context, index). getannotations: inspect.get_annotations(obj) (3.10+). Claude Code generates plugin loaders, dependency injectors, validation decorators, and debug helpers.
CLAUDE.md for inspect
## inspect Stack
- Stdlib: import inspect
- Signature: sig = inspect.signature(fn); sig.parameters
- Source: inspect.getsource(cls_or_fn)
- Type test: inspect.isfunction/isclass/iscoroutinefunction(obj)
- Docstring: inspect.getdoc(obj)
- Stack: inspect.stack()[1] — caller frame info
- Members: inspect.getmembers(obj, predicate)
inspect Introspection Pipeline
# app/reflectutil.py — signature, members, source, stack, docstring, injection
from __future__ import annotations
import inspect
import textwrap
from collections.abc import Callable
from dataclasses import dataclass
from typing import Any, get_type_hints
# ─────────────────────────────────────────────────────────────────────────────
# 1. Callable introspection
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class ParamInfo:
name: str
kind: str # "positional", "keyword", "var_positional", "var_keyword"
annotation: Any # inspect.Parameter.empty if unannotated
default: Any # inspect.Parameter.empty if required
required: bool
@classmethod
def from_param(cls, p: inspect.Parameter) -> "ParamInfo":
kind_map = {
inspect.Parameter.POSITIONAL_ONLY: "positional",
inspect.Parameter.POSITIONAL_OR_KEYWORD: "positional_or_keyword",
inspect.Parameter.VAR_POSITIONAL: "var_positional",
inspect.Parameter.KEYWORD_ONLY: "keyword_only",
inspect.Parameter.VAR_KEYWORD: "var_keyword",
}
return cls(
name=p.name,
kind=kind_map.get(p.kind, str(p.kind)),
annotation=p.annotation,
default=p.default,
required=p.default is inspect.Parameter.empty and p.kind not in (
inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD
),
)
def signature_info(fn: Callable) -> dict[str, Any]:
"""
Return structured information about a callable's signature.
Example:
def greet(name: str, greeting: str = "Hello") -> str: ...
info = signature_info(greet)
info["params"]["name"].required # True
info["params"]["greeting"].default # "Hello"
info["return_annotation"] # str
"""
sig = inspect.signature(fn)
params = {name: ParamInfo.from_param(p) for name, p in sig.parameters.items()}
return {
"name": getattr(fn, "__name__", repr(fn)),
"params": params,
"return_annotation": sig.return_annotation,
"is_async": inspect.iscoroutinefunction(fn),
"is_generator": inspect.isgeneratorfunction(fn),
"is_method": inspect.ismethod(fn),
"docstring": inspect.getdoc(fn) or "",
}
def required_params(fn: Callable) -> list[str]:
"""
Return names of all required (non-default, non-*args/**kwargs) parameters.
Example:
required_params(open) # ["file"]
"""
sig = inspect.signature(fn)
return [
name for name, p in sig.parameters.items()
if p.default is inspect.Parameter.empty and p.kind not in (
inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD
)
]
def call_with_subset(fn: Callable, available: dict[str, Any]) -> Any:
"""
Call fn using only the keyword arguments from `available` that fn accepts.
Useful for dependency injection / loose coupling.
Example:
def handler(user_id: int, db): ...
result = call_with_subset(handler, {"user_id": 1, "db": db, "extra": "ignored"})
"""
sig = inspect.signature(fn)
accepted = {
name for name, p in sig.parameters.items()
if p.kind in (
inspect.Parameter.POSITIONAL_OR_KEYWORD,
inspect.Parameter.KEYWORD_ONLY,
)
}
has_var_kw = any(
p.kind == inspect.Parameter.VAR_KEYWORD for p in sig.parameters.values()
)
if has_var_kw:
kwargs = available
else:
kwargs = {k: v for k, v in available.items() if k in accepted}
return fn(**kwargs)
def annotated_params(fn: Callable) -> dict[str, type]:
"""
Return a dict of {param_name: annotation} for annotated parameters.
Resolves forward references using get_type_hints.
Example:
def f(x: int, y: str = "") -> bool: ...
annotated_params(f) # {"x": int, "y": str}
"""
try:
hints = get_type_hints(fn)
except Exception:
hints = {}
hints.pop("return", None)
return hints
# ─────────────────────────────────────────────────────────────────────────────
# 2. Class and object inspection
# ─────────────────────────────────────────────────────────────────────────────
def public_methods(obj: Any) -> list[str]:
"""
Return names of all public callable members (methods) of obj.
Example:
public_methods(MyClass()) # ["compute", "reset", "save"]
"""
return [
name for name, val in inspect.getmembers(obj, predicate=inspect.ismethod)
if not name.startswith("_")
]
def public_functions(module: Any) -> list[str]:
"""
Return names of all public functions defined in a module (not imported).
Example:
import mymodule
public_functions(mymodule) # ["do_thing", "process"]
"""
mod_file = getattr(module, "__file__", None)
return [
name for name, fn in inspect.getmembers(module, predicate=inspect.isfunction)
if not name.startswith("_")
and (mod_file is None or inspect.getfile(fn) == mod_file)
]
def class_hierarchy(cls: type) -> list[str]:
"""
Return the MRO class names for a class.
Example:
class_hierarchy(int) # ["int", "object"]
"""
return [c.__name__ for c in inspect.getmro(cls)]
def list_subclasses(cls: type, recursive: bool = True) -> list[type]:
"""
Return all known subclasses of cls loaded in the current runtime.
Example:
list_subclasses(BaseHandler) # [JSONHandler, HTMLHandler, ...]
"""
direct = cls.__subclasses__()
if not recursive:
return direct
all_subs: list[type] = []
queue = list(direct)
while queue:
sub = queue.pop()
all_subs.append(sub)
queue.extend(sub.__subclasses__())
return all_subs
def object_summary(obj: Any) -> dict[str, Any]:
"""
Return a summary dict describing obj's type and public interface.
Example:
summary = object_summary(some_instance)
"""
cls = type(obj)
return {
"type": cls.__name__,
"module": cls.__module__,
"mro": class_hierarchy(cls),
"methods": public_methods(obj),
"is_async": inspect.iscoroutinefunction(obj),
"docstring": inspect.getdoc(cls) or "",
"source_file": inspect.getsourcefile(cls) or "built-in",
}
# ─────────────────────────────────────────────────────────────────────────────
# 3. Source code retrieval
# ─────────────────────────────────────────────────────────────────────────────
def get_source(obj: Any) -> str | None:
"""
Return the source code of a function, class, or method.
Returns None if source is unavailable (built-in, compiled extension, REPL).
Example:
print(get_source(MyClass.my_method))
"""
try:
return inspect.getsource(obj)
except (OSError, TypeError):
return None
def get_source_lines(obj: Any) -> tuple[list[str], int] | None:
"""
Return (source_lines, start_lineno) or None if unavailable.
Example:
lines, lineno = get_source_lines(fn) or ([], 0)
"""
try:
return inspect.getsourcelines(obj)
except (OSError, TypeError):
return None
def source_location(obj: Any) -> str:
"""
Return a 'file:lineno' string for obj or '<unknown>' if unavailable.
Example:
source_location(MyClass.method) # "src/myclass.py:42"
"""
try:
file = inspect.getsourcefile(obj) or inspect.getfile(obj)
lines, lineno = inspect.getsourcelines(obj)
return f"{file}:{lineno}"
except (OSError, TypeError):
return "<unknown>"
# ─────────────────────────────────────────────────────────────────────────────
# 4. Call stack helpers
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class FrameSnapshot:
filename: str
lineno: int
function: str
code: str
def caller_info(depth: int = 1) -> FrameSnapshot:
"""
Return info about the caller at the given stack depth.
depth=1 → immediate caller; depth=2 → caller's caller, etc.
Example:
def log(msg):
loc = caller_info(depth=1)
print(f"[{loc.filename}:{loc.lineno}] {msg}")
"""
frame = inspect.stack()[depth + 1]
code = (frame.code_context[0].strip() if frame.code_context else "")
return FrameSnapshot(
filename=frame.filename,
lineno=frame.lineno,
function=frame.function,
code=code,
)
def call_stack(max_depth: int = 10, skip: int = 1) -> list[FrameSnapshot]:
"""
Return a list of FrameSnapshot for the current call stack.
Example:
for frame in call_stack():
print(f" {frame.function} at {frame.filename}:{frame.lineno}")
"""
frames = inspect.stack()[skip:skip + max_depth]
result = []
for f in frames:
code = (f.code_context[0].strip() if f.code_context else "")
result.append(FrameSnapshot(f.filename, f.lineno, f.function, code))
return result
def format_stack(max_depth: int = 8) -> str:
"""
Return a formatted multi-line string of the current call stack (for logging).
Example:
logger.debug("stack at save():\n" + format_stack())
"""
lines = []
for snap in call_stack(max_depth=max_depth, skip=2):
lines.append(f" {snap.function}() @ {snap.filename}:{snap.lineno}")
if snap.code:
lines.append(f" → {snap.code}")
return "\n".join(lines)
# ─────────────────────────────────────────────────────────────────────────────
# 5. Plugin / registry helpers
# ─────────────────────────────────────────────────────────────────────────────
def find_implementations(base_cls: type, module: Any) -> list[type]:
"""
Find all concrete subclasses of base_cls defined in module.
Example:
handlers = find_implementations(BaseHandler, handlers_module)
"""
return [
cls for _, cls in inspect.getmembers(module, predicate=inspect.isclass)
if issubclass(cls, base_cls) and cls is not base_cls and not inspect.isabstract(cls)
and cls.__module__ == getattr(module, "__name__", None)
]
def auto_wire(cls: type, registry: dict[str, Any]) -> Any:
"""
Instantiate cls by injecting constructor parameters from registry by name.
Example:
registry = {"db": db_conn, "cache": cache, "logger": logger}
service = auto_wire(UserService, registry)
"""
return call_with_subset(cls, registry)
def method_table(obj: Any) -> dict[str, dict[str, Any]]:
"""
Return a dict mapping public method names to their signature info.
Example:
table = method_table(my_service)
for name, info in table.items():
print(f" {name}({', '.join(info['params'])})")
"""
return {
name: {"params": list(signature_info(method)["params"].keys()),
"is_async": inspect.iscoroutinefunction(method),
"docstring": inspect.getdoc(method) or ""}
for name in public_methods(obj)
for method in [getattr(obj, name)]
}
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
import asyncio
print("=== inspect demo ===")
print("\n--- signature_info ---")
def process(
items: list[str],
limit: int = 100,
*,
verbose: bool = False,
callback: Callable | None = None,
) -> dict:
"""Process a list of items up to limit."""
...
info = signature_info(process)
print(f" name: {info['name']}")
for name, p in info["params"].items():
req = "required" if p.required else f"default={p.default!r}"
print(f" param {name!r}: kind={p.kind} {req}")
print(f" return: {info['return_annotation']}")
print(f" is_async: {info['is_async']}")
print("\n--- required_params ---")
print(f" required_params(process): {required_params(process)}")
print(f" required_params(print): {required_params(print)}")
print("\n--- call_with_subset ---")
def add(x: int, y: int) -> int:
return x + y
result = call_with_subset(add, {"x": 10, "y": 20, "z": 99, "extra": "ignored"})
print(f" add called with subset: {result}")
print("\n--- annotated_params ---")
def login(username: str, password: str, remember: bool = False) -> bool: ...
print(f" annotated_params(login): {annotated_params(login)}")
print("\n--- class_hierarchy ---")
print(f" class_hierarchy(bool): {class_hierarchy(bool)}")
print("\n--- list_subclasses ---")
class Animal:
pass
class Dog(Animal):
pass
class Cat(Animal):
pass
class Labrador(Dog):
pass
subs = list_subclasses(Animal)
print(f" subclasses of Animal: {[c.__name__ for c in subs]}")
print("\n--- source code ---")
src = get_source(add)
if src:
print(f" source of add():\n{textwrap.indent(src.strip(), ' ')}")
loc = source_location(process)
print(f" source_location(process): {loc}")
print("\n--- call_stack ---")
def inner():
return call_stack(max_depth=3)
def outer():
return inner()
stack = outer()
for snap in stack:
print(f" {snap.function}() @ ...:{snap.lineno}")
print("\n--- method_table ---")
class Calculator:
"""Simple calculator."""
def add(self, a: int, b: int) -> int:
"""Add two numbers."""
return a + b
def multiply(self, a: int, b: int) -> int:
"""Multiply two numbers."""
return a * b
async def async_compute(self, x: float) -> float:
"""Async compute."""
return x * 2.0
calc = Calculator()
table = method_table(calc)
for name, meta in table.items():
async_label = " [async]" if meta["is_async"] else ""
print(f" {name}({', '.join(meta['params'])}){async_label} — {meta['docstring']}")
print("\n--- auto_wire ---")
class Logger:
def log(self, msg: str) -> None:
print(f" [LOG] {msg}")
class ReportService:
def __init__(self, logger: Logger, title: str = "Report") -> None:
self.logger = logger
self.title = title
def run(self) -> None:
self.logger.log(f"Running '{self.title}'")
logger_instance = Logger()
svc = auto_wire(ReportService, {"logger": logger_instance, "title": "Sales Report"})
svc.run()
print("\n=== done ===")
For the dir / vars alternative — Python’s built-ins dir(obj) returns all attribute names including inherited ones, vars(obj) returns obj.__dict__, and type(obj).__mro__ gives the method resolution order; they are fast and always available but return raw names with no metadata about kinds, defaults, or annotations; inspect provides structured Parameter, Signature, FrameInfo, and MemberDescriptorType objects with full annotations — use dir/vars for quick interactive exploration or simple attribute checks, inspect when you need structural information for frameworks, decorators, code generators, or debugging tools. For the ast alternative — Python’s ast module parses source text into an Abstract Syntax Tree for static analysis: ast.parse(source), ast.walk(tree), ast.NodeVisitor; inspect works on live objects and retrieves source via getsource (which may fail for REPL/eval code); ast does not require importing the module — use ast for linters, code transformers, security scanners, and static import graph analysis, inspect for runtime plugins, dependency injectors, debuggers, and reflection APIs that operate on already-loaded objects. The Claude Skills 360 bundle includes inspect skill sets covering ParamInfo/signature_info()/required_params() signature introspection, call_with_subset()/annotated_params() dependency injection helpers, public_methods()/public_functions()/class_hierarchy()/list_subclasses()/object_summary() class reflection, get_source()/source_location() source retrieval, FrameSnapshot/caller_info()/call_stack()/format_stack() stack inspection, and find_implementations()/auto_wire()/method_table() plugin and registry utilities. Start with the free tier to try runtime introspection and inspect pipeline code generation.