Python inspect 模块:运行时获取模型结构、参数与类型信息
大家好,今天我们来深入探讨 Python 的 inspect 模块。这个模块提供了一组强大的工具,允许我们在运行时检查 Python 对象(包括模块、类、函数、方法、代码对象、帧对象和跟踪对象等)的内部结构、参数信息和类型信息。这对于构建动态系统、元编程、调试工具以及框架开发至关重要。
inspect 模块的核心功能
inspect 模块的主要目标是提供自省 (introspection) 能力。 自省是指程序在运行时检查自身结构的能力。 换句话说,我们可以利用 inspect 模块来“窥探”程序内部,了解对象是如何定义的,包含哪些属性和方法,以及如何被调用。
inspect 模块主要包含以下几个方面的功能:
- 类型检查: 确定对象的类型(例如,是否是函数、类、模块等)。
- 获取源代码: 获取函数、类或方法的源代码。
- 获取参数信息: 获取函数的参数列表、默认值和类型注解。
- 获取类成员: 获取类的属性、方法和其他成员。
- 获取调用栈信息: 获取当前调用栈,用于调试和性能分析。
- 判断对象是否可调用: 检查对象是否可以像函数一样被调用。
- 模块和包的分析: 获取模块和包的信息,包括导入的模块和包含的对象。
常用函数详解与实例
下面我们详细介绍 inspect 模块中一些常用的函数,并通过实例演示如何使用它们。
1. inspect.getmembers(object[, predicate])
这个函数返回一个包含对象所有成员的列表,每个成员是一个 (name, value) 元组。 predicate 是一个可选的过滤函数,用于筛选返回的成员。
import inspect
class MyClass:
"""A simple class for demonstration."""
class_attribute = "Class Attribute"
def __init__(self, instance_attribute):
self.instance_attribute = instance_attribute
def my_method(self, arg1, arg2):
"""A simple method."""
return arg1 + arg2
def my_function(arg1, arg2):
"""A simple function."""
return arg1 * arg2
# 获取 MyClass 的所有成员
members = inspect.getmembers(MyClass)
print("MyClass members:", members)
# 使用 predicate 过滤,只获取方法
methods = inspect.getmembers(MyClass, inspect.isfunction) # 注意,这里用isfunction而不是ismethod
print("nMyClass methods:", methods)
# 获取 my_function 的所有成员 (通常是空列表,因为函数没有成员)
function_members = inspect.getmembers(my_function)
print("nmy_function members:", function_members)
输出:
MyClass members: [('__annotations__', {}), ('__class__', <class 'type'>), ('__delattr__', <slot wrapper '__delattr__' of 'object' objects>), ('__dict__', <attribute '__dict__' of 'MyClass' objects>), ('__dir__', <built-in method __dir__ of type object at 0x...>), ('__doc__', 'A simple class for demonstration.'), ('__eq__', <slot wrapper '__eq__' of 'object' objects>), ('__format__', <built-in method __format__ of 'object' objects>), ('__ge__', <slot wrapper '__ge__' of 'object' objects>), ('__getattribute__', <slot wrapper '__getattribute__' of 'object' objects>), ('__gt__', <slot wrapper '__gt__' of 'object' objects>), ('__hash__', <slot wrapper '__hash__' of 'object' objects>), ('__init__', <function MyClass.__init__ at 0x...>), ('__init_subclass__', <built-in method __init_subclass__ of type object at 0x...>), ('__le__', <slot wrapper '__le__' of 'object' objects>), ('__lt__', <slot wrapper '__lt__' of 'object' objects>), ('__module__', '__main__'), ('__ne__', <slot wrapper '__ne__' of 'object' objects>), ('__new__', <built-in method __new__ of type object at 0x...>), ('__reduce__', <built-in method __reduce__ of 'object' objects>), ('__reduce_ex__', <built-in method __reduce_ex__ of 'object' objects>), ('__repr__', <slot wrapper '__repr__' of 'object' objects>), ('__setattr__', <slot wrapper '__setattr__' of 'object' objects>), ('__sizeof__', <built-in method __sizeof__ of 'object' objects>), ('__str__', <slot wrapper '__str__' of 'object' objects>), ('__subclasshook__', <built-in method __subclasshook__ of type object at 0x...>), ('class_attribute', 'Class Attribute'), ('my_method', <function MyClass.my_method at 0x...>)
]
MyClass methods: [('my_method', <function MyClass.my_method at 0x...>)]
my_function members: []
2. 类型检查函数
inspect 模块提供了一系列类型检查函数,用于判断对象的类型。常用的函数包括:
inspect.ismodule(object): 如果对象是模块,返回True。inspect.isclass(object): 如果对象是类,返回True。inspect.isfunction(object): 如果对象是函数,返回True。inspect.ismethod(object): 如果对象是绑定方法,返回True。inspect.isbuiltin(object): 如果对象是内建函数或方法,返回True。inspect.isgeneratorfunction(object): 如果对象是生成器函数,返回True。inspect.isgenerator(object): 如果对象是生成器,返回True。inspect.istraceback(object): 如果对象是 traceback,返回True。inspect.isframe(object): 如果对象是 frame,返回True。inspect.iscode(object): 如果对象是 code object,返回True。inspect.isabstract(object): 如果对象是抽象类,返回True。inspect.isawaitable(object): 如果对象是 awaitable, 返回True。inspect.iscoroutinefunction(object): 如果对象是协程函数,返回True。inspect.iscoroutine(object): 如果对象是协程,返回True。inspect.isasyncgenfunction(object): 如果对象是异步生成器函数,返回True。inspect.isasyncgen(object): 如果对象是异步生成器,返回True。
import inspect
def my_function():
pass
class MyClass:
def my_method(self):
pass
instance = MyClass()
print("my_function is function:", inspect.isfunction(my_function))
print("MyClass is class:", inspect.isclass(MyClass))
print("instance.my_method is method:", inspect.ismethod(instance.my_method))
print("len is builtin:", inspect.isbuiltin(len))
输出:
my_function is function: True
MyClass is class: True
instance.my_method is method: True
len is builtin: True
3. inspect.signature(callable, *, follow_wrapped=True)
这个函数返回一个 Signature 对象,表示可调用对象的调用签名。 Signature 对象包含了参数信息,包括参数名称、类型注解和默认值。 follow_wrapped 参数指定是否跟随 __wrapped__ 属性以获取原始签名(如果存在)。
import inspect
def my_function(arg1: int, arg2: str = "default") -> bool:
"""A function with type annotations and a default value."""
return True
sig = inspect.signature(my_function)
print("Signature:", sig)
for param in sig.parameters.values():
print("Parameter name:", param.name)
print("Parameter kind:", param.kind)
print("Parameter default:", param.default)
print("Parameter annotation:", param.annotation)
print("-" * 20)
print("Return annotation:", sig.return_annotation)
输出:
Signature: (arg1: int, arg2: str = 'default') -> bool
Parameter name: arg1
Parameter kind: POSITIONAL_OR_KEYWORD
Parameter default: <class 'inspect._empty'>
Parameter annotation: <class 'int'>
--------------------
Parameter name: arg2
Parameter kind: POSITIONAL_OR_KEYWORD
Parameter default: default
Parameter annotation: <class 'str'>
--------------------
Return annotation: <class 'bool'>
Parameter.kind 属性的取值:
| 取值 | 含义 |
|---|---|
POSITIONAL_OR_KEYWORD |
可以通过位置或关键字传递的参数 |
VAR_POSITIONAL |
位置可变参数(*args) |
VAR_KEYWORD |
关键字可变参数(**kwargs) |
KEYWORD_ONLY |
只能通过关键字传递的参数 |
POSITIONAL_ONLY |
只能通过位置传递的参数 (Python 3.8+) |
4. inspect.getsource(object) 和 inspect.getsourcefile(object)
inspect.getsource(object): 返回对象的源代码。如果对象是模块,返回整个模块的源代码。inspect.getsourcefile(object): 返回对象定义所在的源文件名。
import inspect
def my_function():
"""This is a docstring."""
print("Hello, world!")
print("Source code:n", inspect.getsource(my_function))
print("Source file:", inspect.getsourcefile(my_function))
输出:
Source code:
def my_function():
"""This is a docstring."""
print("Hello, world!")
Source file: /.../inspect_example.py
5. inspect.getdoc(object)
返回对象的文档字符串(docstring)。
import inspect
def my_function():
"""This is a docstring."""
print("Hello, world!")
print("Docstring:", inspect.getdoc(my_function))
输出:
Docstring: This is a docstring.
6. inspect.getmodule(object)
尝试猜测对象定义在哪个模块。
import inspect
import os
print("Module:", inspect.getmodule(os.path))
输出:
Module: <module 'posixpath' from '/.../posixpath.py'>
7. inspect.currentframe() 和 inspect.stack()
inspect.currentframe(): 返回当前执行帧对象。inspect.stack([context]): 返回当前调用栈的帧对象列表。context参数指定要返回的上下文行数。
这些函数主要用于调试和性能分析。
import inspect
def my_function():
frame = inspect.currentframe()
print("Current frame:", frame)
stack = inspect.stack()
print("Call stack:", stack)
my_function()
输出 (部分):
Current frame: <frame object at 0x...>
Call stack: [FrameInfo(frame=<frame object at 0x...>, filename='/.../inspect_example.py', lineno=10, function='my_function', code_context=[' stack = inspect.stack()n'], index=0), FrameInfo(frame=<frame object at 0x...>, filename='/.../inspect_example.py', lineno=13, function='<module>', code_context=['my_function()n'], index=0)]
8. inspect.getattr_static(object, attr)
获取对象的属性,但不会触发描述器协议 (__get__)。 这对于获取类属性而不是实例属性非常有用。
import inspect
class MyClass:
class_attribute = "Class Attribute"
@property
def instance_attribute(self):
return "Instance Attribute"
instance = MyClass()
print("Class attribute (static):", inspect.getattr_static(MyClass, "class_attribute"))
print("Instance attribute (static):", inspect.getattr_static(MyClass, "instance_attribute")) # 获取的是 property 对象,而不是属性值
print("Class attribute (dynamic):", getattr(MyClass, "class_attribute"))
print("Instance attribute (dynamic):", instance.instance_attribute) # 获取的是 property 对应的值
输出:
Class attribute (static): Class Attribute
Instance attribute (static): <property object at 0x...>
Class attribute (dynamic): Class Attribute
Instance attribute (dynamic): Instance Attribute
9. inspect.findsource(object)
尝试查找对象的源代码和起始行号。 返回一个元组 (source, filename),其中 source 是源代码字符串列表,filename 是文件名。
import inspect
def my_function():
"""This is a docstring."""
print("Hello, world!")
source, filename = inspect.findsource(my_function)
print("Source code (lines):n", source)
print("Filename:", filename)
输出:
Source code (lines):
['def my_function():n', ' """This is a docstring."""n', ' print("Hello, world!")n']
Filename: /.../inspect_example.py
inspect 模块的应用场景
inspect 模块的应用非常广泛,以下是一些常见的场景:
- 调试器和诊断工具:
inspect模块可以用来获取调用栈、变量值和对象信息,从而帮助开发者调试代码。 - 代码生成器: 可以分析已有的类或函数,并根据其结构生成新的代码。
- 单元测试框架: 可以动态地发现和执行测试用例。
- 对象序列化和反序列化: 可以检查对象的属性和类型,以便正确地序列化和反序列化对象。
- 依赖注入框架: 可以分析构造函数的参数,并自动注入依赖项。
- 自动文档生成: 可以提取文档字符串并生成 API 文档。
- 框架开发:
inspect模块可以用来实现框架的各种功能,例如路由、配置和插件机制。
实例:自定义一个简单的依赖注入容器
下面我们用 inspect 模块实现一个简单的依赖注入容器。
import inspect
class Container:
def __init__(self):
self.dependencies = {}
def register(self, name, obj):
"""注册一个依赖项."""
self.dependencies[name] = obj
def resolve(self, target):
"""解析目标对象的依赖项并创建实例."""
sig = inspect.signature(target)
params = []
for param in sig.parameters.values():
if param.annotation is inspect._empty:
raise ValueError(f"Missing type annotation for parameter {param.name} in {target.__name__}")
dependency = self.dependencies.get(param.annotation)
if dependency is None:
raise ValueError(f"Dependency {param.annotation} not found for {target.__name__}")
params.append(dependency)
return target(*params)
# 示例依赖项
class DatabaseConnection:
def __init__(self):
self.connection_string = "localhost:5432"
def connect(self):
print(f"Connecting to {self.connection_string}")
class UserRepository:
def __init__(self, db_connection: DatabaseConnection):
self.db_connection = db_connection
def get_user(self, user_id):
self.db_connection.connect()
return f"User {user_id}"
# 使用容器
container = Container()
container.register(DatabaseConnection, DatabaseConnection())
# 注册 UserRepository 时,需要 DatabaseConnection 依赖
user_repository = container.resolve(UserRepository) # 创建 UserRepository 实例并注入依赖
user = user_repository.get_user(123)
print(user)
在这个例子中,Container 类使用 inspect.signature 来获取 UserRepository 构造函数的参数信息,并根据参数的类型注解从容器中查找依赖项。 如果没有找到依赖项或者缺少类型注解,会抛出异常。
inspect 模块的局限性
inspect 模块虽然强大,但也存在一些局限性:
- 性能开销: 运行时检查会引入额外的性能开销,尤其是在复杂的代码中。
- 动态性限制:
inspect模块主要用于检查静态结构。 对于完全动态生成的代码(例如,使用exec或eval创建的代码),可能无法提供完整的自省能力。 - C 扩展模块: 对于使用 C 编写的扩展模块,
inspect模块可能无法获取所有信息。 - 类型提示的可靠性:
inspect模块依赖于类型提示的准确性。 如果类型提示不正确,inspect模块返回的信息也可能不正确。
尽管存在这些局限性,inspect 模块仍然是 Python 中进行自省和元编程的重要工具。
使用场景广泛,需要注意性能
inspect 模块提供了强大的自省能力,在调试、代码生成、框架开发等领域都有广泛的应用。 虽然存在一些局限性,但它仍然是 Python 开发者不可或缺的工具之一。 使用时需要注意性能开销,避免在性能敏感的代码中使用过多的自省操作。
更多IT精英技术系列讲座,到智猿学院