Vanson's Eternal Blog

Python类库Inspect

Python inspect basic.png
Published on
/10 mins read/---

Inspect

Python 标准库 inspect 是**“运行时自省(introspection)”**的瑞士军刀。

它把解释器内部对象(模块、类、函数、方法、代码对象、协程、帧、回溯、生成器等)拆成“乐高积木”

  • 提取签名、注解、源码、字节码、局部变量
  • 判断对象种类(可调用?协程?内置?绑定方法?)
  • 遍历类/模块成员并按类型过滤
  • 解析堆栈帧与回溯
  • 实现装饰器、RPC、序列化、调试器、IDE、ORM、测试框架等高级功能
类别关键函数/类说明
Kind checkismodule, isclass, isfunction, ismethod, isbuiltin, ismethoddescriptor, isdatadescriptor, isgenerator, iscoroutine, isasyncgen, isframe, istraceback, iscode精确区分对象种类
Signature & annotationsignature(), Signature, Parameter, getfullargspec(), get_annotations()提取调用签名、默认值、注解、PEP 570 positional-only
Source & bytecodegetsource(), getsourcelines(), getsourcefile(), getfile(), getcomments(), getclosurevars(), getframeinfo()拿到源码、行号、闭包变量
Member enumerationgetmembers(), getmembers_static(), getmodulename(), getmoduleinfo(), getclasstree()遍历成员并按类型过滤
Stack & tracebackcurrentframe(), stack(), trace()调试、日志、Profiler
Code object 细节getargvalues(), formatargvalues(), getargvalues()低层字节码操作
Import hooksgetmodule(), findsource(), getabsfile()与 importlib 配合

原理

inspect 模块的核心基于 Python 的 数据模型 和 执行模型:

  • 函数/类对象:利用 __annotations____code__ 等特殊属性
  • 栈帧(Frame):通过 sys._getframe() 获取当前执行上下文
  • 签名系统:解析函数的 __code__.co_varnames__defaults__
# 手动实现获取函数参数名(简化版)
def get_args(func):
    code = func.__code__
    return code.co_varnames[:code.co_argcount]
 
def example(a, b=1): pass
print(get_args(example))  # 输出: ('a', 'b')

API使用

类型检查 (Kind Check)

import inspect
import math
from collections import defaultdict
 
def example_function():
    pass
 
class ExampleClass:
    def instance_method(self):
        pass
 
    @classmethod
    def class_method(cls):
        pass
 
    @staticmethod
    def static_method():
        pass
 
# 各种类型检查
print("ismodule(math):", inspect.ismodule(math))  # True
print("isclass(ExampleClass):", inspect.isclass(ExampleClass))  # True
print("isfunction(example_function):", inspect.isfunction(example_function))  # True
print("ismethod(ExampleClass().instance_method):",
      inspect.ismethod(ExampleClass().instance_method))  # True
print("isbuiltin(len):", inspect.isbuiltin(len))  # True
print("ismethoddescriptor(str.lower):",
      inspect.ismethoddescriptor(str.lower))  # True
print("isdatadescriptor(defaultdict.default_factory):",
      inspect.isdatadescriptor(defaultdict.default_factory))  # True
 
# 生成器与协程检查
def generator_func():
    yield 1
 
async def async_func():
    await asyncio.sleep(0)
 
print("isgeneratorfunction(generator_func):",
      inspect.isgeneratorfunction(generator_func))  # True
print("iscoroutinefunction(async_func):",
      inspect.iscoroutinefunction(async_func))  # True
 
# 代码对象检查
code_obj = example_function.__code__
print("iscode(code_obj):", inspect.iscode(code_obj))  # True

签名与注解

def complex_function(
    pos_arg,
    /,
    pos_or_kw: int,
    *args: float,
    kw_arg: str = "default",
    **kwargs: dict
) -> bool:
    """示例函数"""
    return True
 
# 获取函数签名
sig = inspect.signature(complex_function)
print("Signature:", sig)
# (pos_arg, /, pos_or_kw: int, *args: float, kw_arg: str = 'default', **kwargs: dict) -> bool
 
# 分析参数
for name, param in sig.parameters.items():
    print(f"\nParameter: {name}")
    print(f"  Kind: {param.kind}")
    print(f"  Default: {param.default if param.default is not param.empty else 'No default'}")
    print(f"  Annotation: {param.annotation if param.annotation is not param.empty else 'No annotation'}")
 
# 获取返回值注解
print("\nReturn annotation:", sig.return_annotation)  # <class 'bool'>
 
# 旧版获取参数规范 (Python 3.5+ 推荐使用 signature)
argspec = inspect.getfullargspec(complex_function)
print("\nArgspec:", argspec)
# FullArgSpec(args=['pos_arg', 'pos_or_kw'], varargs='args', varkw='kwargs',
# defaults=('default',), kwonlyargs=[], kwonlydefaults=None, annotations=...)

源代码操作

def sample_function(x, y):
    """计算两个数的和"""
    result = x + y
    return result
 
# 获取源代码
source = inspect.getsource(sample_function)
print("Source code:\n", source)
 
# 获取源代码行
lines, start_line = inspect.getsourcelines(sample_function)
print(f"\nSource lines (starting at line {start_line}):")
for i, line in enumerate(lines, start_line):
    print(f"{i}: {line.rstrip()}")
 
# 获取文件信息
print("\nSource file:", inspect.getsourcefile(sample_function))
print("Module file:", inspect.getfile(inspect))
 
# 获取注释
print("\nComments:", inspect.getcomments(sample_function))
 
# 闭包变量检查
def outer_func(x):
    def inner_func(y):
        return x + y
    return inner_func
 
closure_func = outer_func(10)
closure_vars = inspect.getclosurevars(closure_func)
print("\nClosure variables:", closure_vars)
# ClosureVars(nonlocals={'x': 10}, globals={}, builtins={}, unbound=set())
 
# 获取栈帧信息
def get_frame_info():
    frame = inspect.currentframe()
    frame_info = inspect.getframeinfo(frame)
    return frame_info
 
frame_info = get_frame_info()
print("\nFrame info:")
print(f"File: {frame_info.filename}")
print(f"Line: {frame_info.lineno}")
print(f"Function: {frame_info.function}")
print(f"Code context: {frame_info.code_context}")

成员枚举

class ExampleClass:
    class_attr = 42
 
    def __init__(self):
        self.instance_attr = 100
 
    def public_method(self):
        pass
 
    def _protected_method(self):
        pass
 
    def __private_method(self):
        pass
 
    @classmethod
    def cls_method(cls):
        pass
 
    @staticmethod
    def static_method():
        pass
 
# 获取所有成员
all_members = inspect.getmembers(ExampleClass)
print("\nAll members:")
for name, member in all_members:
    print(f"{name}: {type(member).__name__}")
 
# 过滤特定类型的成员
print("\nMethods:")
methods = inspect.getmembers(ExampleClass, inspect.ismethod)
for name, method in methods:
    print(name)
 
print("\nFunctions:")
functions = inspect.getmembers(ExampleClass, inspect.isfunction)
for name, func in functions:
    print(name)
 
# 静态获取成员(避免触发描述符)
print("\nStatic members:")
static_members = inspect.getmembers_static(ExampleClass)
for name, member in static_members:
    print(f"{name}: {type(member).__name__}")
 
# 获取类层次结构
class Base: pass
class Child(Base): pass
class GrandChild(Child): pass
 
class_tree = inspect.getclasstree([GrandChild])
print("\nClass tree:")
print(class_tree)

栈与回溯

import traceback
 
def level_3():
    # 获取当前栈帧
    current_frame = inspect.currentframe()
    print("\nCurrent frame:", current_frame)
 
    # 获取调用栈
    stack = inspect.stack()
    print("\nCall stack:")
    for frame_info in stack:
        print(f"Function: {frame_info.function}, Line: {frame_info.lineno}, File: {frame_info.filename}")
 
    # 获取外部帧
    outer_frame = current_frame.f_back
    outer_frame_info = inspect.getframeinfo(outer_frame)
    print("\nOuter frame info:")
    print(f"Function: {outer_frame_info.function}")
    print(f"Line: {outer_frame_info.lineno}")
    print(f"Code: {outer_frame_info.code_context[0].strip() if outer_frame_info.code_context else None}")
 
def level_2():
    level_3()
 
def level_1():
    level_2()
 
# 生成调用栈
level_1()
 
# 回溯信息
try:
    1 / 0
except ZeroDivisionError:
    tb = inspect.trace()
    print("\nTraceback:")
    for frame_info in tb:
        print(f"File {frame_info.filename}, line {frame_info.lineno}, in {frame_info.function}")
        print(f"  {frame_info.code_context[0].strip() if frame_info.code_context else ''}")

代码对象细节

def sample_func(a, b=2, *args, **kwargs):
    x = 10
    y = 20
    return a + b
 
# 获取代码对象
code_obj = sample_func.__code__
 
# 获取参数信息
arg_info = inspect.getargvalues(inspect.currentframe())
print("\nArgument values:", arg_info)
# ArgInfo(args=['a', 'b'], varargs='args', keywords='kwargs', locals={'a': ..., 'b': ...})
 
# 格式化参数值
formatted_args = inspect.formatargvalues(*inspect.getargvalues(inspect.currentframe()))
print("\nFormatted arguments:", formatted_args)
 
# 分析代码对象属性
print("\nCode object attributes:")
print(f"co_name: {code_obj.co_name}")  # 函数名
print(f"co_argcount: {code_obj.co_argcount}")  # 位置参数数量
print(f"co_posonlyargcount: {code_obj.co_posonlyargcount}")  # 仅位置参数数量
print(f"co_kwonlyargcount: {code_obj.co_kwonlyargcount}")  # 仅关键字参数数量
print(f"co_nlocals: {code_obj.co_nlocals}")  # 局部变量数量
print(f"co_stacksize: {code_obj.co_stacksize}")  # 所需栈空间
print(f"co_flags: {code_obj.co_flags}")  # 标志位
print(f"co_code: {code_obj.co_code[:20]}...")  # 字节码

导入钩子

# 获取模块信息
module = inspect.getmodule(inspect)
print("\nModule of inspect:", module)  # <module 'inspect' from ...>
 
# 查找模块源文件
source_file = inspect.getsourcefile(inspect)
print("\nSource file of inspect:", source_file)
 
# 获取模块绝对路径
abs_file = inspect.getabsfile(inspect)
print("\nAbsolute file path:", abs_file)
 
# 获取模块名
module_name = inspect.getmodulename(__file__)
print("\nCurrent module name:", module_name)
 
# 查找模块源文件(旧版)
try:
    module_info = inspect.getmoduleinfo(inspect.__file__)
    print("\nModule info:", module_info)
except TypeError:
    print("\ngetmoduleinfo is deprecated and removed in Python 3.13")

场景

综合案例

import inspect, json, functools
 
def expose(fn):
    """装饰器:把函数签名暴露为 RPC 描述"""
    sig = inspect.signature(fn)
    params = [(p.name, str(p.annotation) if p.annotation != inspect.Parameter.empty else 'Any')
              for p in sig.parameters.values()]
    fn._rpc = {'name': fn.__name__, 'params': params, 'return': str(sig.return_annotation)}
    return fn
 
class Service:
    @expose
    def add(self, x: int, y: int) -> int:
        return x + y
 
# 运行时自省
for name, method in inspect.getmembers(Service, inspect.isfunction):
    print(method._rpc)
# {'name': 'add', 'params': [('x', "<class 'int'>"), ('y', "<class 'int'>")], 'return': "<class 'int'>"}

函数装饰器增强

def log_call(func):
    sig = inspect.signature(func)
 
    def wrapper(*args, **kwargs):
        bound = sig.bind(*args, **kwargs)
        args_str = ", ".join(f"{k}={v}" for k, v in bound.arguments.items())
        print(f"调用 {func.__name__}({args_str})")
        return func(*args, **kwargs)
 
    return wrapper
 
@log_call
def multiply(x: float, y: float) -> float:
    """计算两数乘积"""
    return x * y
 
multiply(3, y=4)
# 输出: 调用 multiply(x=3, y=4)
# 返回: 12

动态参数验证器

def validate_arguments(func, *args, **kwargs):
    """验证函数参数是否符合签名要求"""
    try:
        sig = inspect.signature(func)
        bound_args = sig.bind(*args, **kwargs)
        bound_args.apply_defaults()
        print("✅ Arguments valid!")
        return True
    except TypeError as e:
        print(f"❌ Argument error: {e}")
        return False
 
def example(a: int, b: float = 3.14) -> float:
    return a * b
 
validate_arguments(example, 5)          # ✅ Arguments valid!
validate_arguments(example, "five")     # ❌ Argument error: ...
validate_arguments(example, 5, 2.0)     # ✅ Arguments valid!
validate_arguments(example, 5, b=2.0)   # ✅ Arguments valid!

自动文档生成器

def generate_docs(func):
    """自动生成函数文档"""
    sig = inspect.signature(func)
    doc = {
        "name": func.__name__,
        "signature": str(sig),
        "docstring": inspect.getdoc(func) or "No documentation",
        "parameters": [],
        "return": sig.return_annotation if sig.return_annotation is not sig.empty else "None"
    }
 
    for name, param in sig.parameters.items():
        param_info = {
            "name": name,
            "kind": param.kind.name,
            "default": param.default if param.default is not param.empty else None,
            "annotation": param.annotation if param.annotation is not param.empty else "Any"
        }
        doc["parameters"].append(param_info)
 
    return doc
 
def sample_func(a: int, b: float = 1.0) -> float:
    """计算两个数的乘积
 
    Args:
        a: 第一个整数
        b: 第二个浮点数,默认为1.0
 
    Returns:
        两数的乘积
    """
    return a * b
 
print("\nAuto-generated documentation:")
print(generate_docs(sample_func))

对象序列化器

def serialize_object(obj):
    """序列化Python对象为可JSON化的结构"""
    if inspect.ismodule(obj):
        return {"type": "module", "name": obj.__name__}
 
    if inspect.isclass(obj):
        return {
            "type": "class",
            "name": obj.__name__,
            "module": obj.__module__,
            "bases": [base.__name__ for base in obj.__bases__]
        }
 
    if inspect.isfunction(obj) or inspect.ismethod(obj):
        return {
            "type": "function",
            "name": obj.__name__,
            "module": obj.__module__,
            "signature": str(inspect.signature(obj))
        }
 
    if isinstance(obj, (int, float, str, bool, type(None))):
        return obj
 
    if isinstance(obj, (list, tuple)):
        return [serialize_object(item) for item in obj]
 
    if isinstance(obj, dict):
        return {key: serialize_object(value) for key, value in obj.items()}
 
    return str(obj)
 
# 测试序列化
class SampleClass:
    def sample_method(self, x):
        return x * 2
 
data = {
    "module": math,
    "class": SampleClass,
    "function": serialize_object,
    "method": SampleClass().sample_method,
    "value": 42,
    "list": [1, 2, 3]
}
 
print("\nSerialized object:")
import pprint
pprint.pprint(serialize_object(data))

最佳实践

性能考虑

  • inspect.stack() 和 inspect.currentframe() 开销较大,避免在性能关键路径中使用
  • 对于频繁调用的函数,缓存 inspect.signature() 结果

环境限制

  • 在压缩/混淆的代码中无法获取源码
  • 动态生成的代码(如 exec() 创建)支持有限
  • C 扩展函数(如内置函数)无法获取完整签名

替代方案

  • 对于简单类型检查,优先使用 isinstance() 和 issubclass()
  • 对于参数验证,考虑使用 typing 模块和类型检查器
  • 对于文档生成,使用 Sphinx 等专业工具

版本兼容

  • inspect.getargspec() 在 Python 3.5+ 中已弃用,使用 inspect.signature() 替代
  • inspect.getmoduleinfo() 在 Python 3.13+ 中已移除

安全考虑

  • 避免在生产环境暴露源码获取功能
  • 谨慎处理来自不可信源的函数签名
← Previous postSecurity夯实基础
Next post →Python框架Flask