Python中的`inspect`模块:运行时获取模型结构、参数与类型信息

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 模块主要用于检查静态结构。 对于完全动态生成的代码(例如,使用 execeval 创建的代码),可能无法提供完整的自省能力。
  • C 扩展模块: 对于使用 C 编写的扩展模块,inspect 模块可能无法获取所有信息。
  • 类型提示的可靠性: inspect 模块依赖于类型提示的准确性。 如果类型提示不正确,inspect 模块返回的信息也可能不正确。

尽管存在这些局限性,inspect 模块仍然是 Python 中进行自省和元编程的重要工具。

使用场景广泛,需要注意性能

inspect 模块提供了强大的自省能力,在调试、代码生成、框架开发等领域都有广泛的应用。 虽然存在一些局限性,但它仍然是 Python 开发者不可或缺的工具之一。 使用时需要注意性能开销,避免在性能敏感的代码中使用过多的自省操作。

更多IT精英技术系列讲座,到智猿学院

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注