Python `inspect` 模块:获取运行时对象与代码元信息

好的,各位观众,欢迎来到今天的“Python inspect 模块:获取运行时对象与代码元信息”主题讲座!我是你们今天的导游,带大家深入探索 inspect 这个 Python 的侦探工具。

引言:代码的X光机

想象一下,你是一位医生,但你的病人不是人,而是 Python 代码。你需要了解代码的内部结构、功能、甚至它的祖宗十八代(继承关系)。你怎么办?难道要一行一行地读代码?那效率也太低了!

这时候,inspect 模块就闪亮登场了。它就像一个 X 光机,可以让你在运行时透视 Python 对象,获取它的各种元信息,比如:

  • 代码的定义位置:这个函数/类是在哪个文件,哪一行定义的?
  • 函数的参数:这个函数需要哪些参数,它们有没有默认值?
  • 对象的属性:这个对象有哪些属性,它们的值是什么?
  • 继承关系:这个类继承自哪些类?
  • 源代码:直接获取函数的源代码!

有了 inspect,你就可以像侦探一样,轻松地分析代码,调试程序,甚至可以动态地生成代码!

第一部分:inspect 模块的核心函数

inspect 模块提供了大量的函数,但我们不需要全部掌握。我们先来学习几个最核心、最常用的函数。

  1. inspect.getmembers(object[, predicate]):列出对象的成员

    这个函数就像一个“成员清单”,可以列出对象的所有成员(属性、方法等等),以及它们的值。

    • object:要检查的对象。
    • predicate (可选):一个过滤器函数,用于选择特定的成员。
    import inspect
    
    class MyClass:
        def __init__(self, x, y):
            self.x = x
            self.y = y
    
        def add(self):
            return self.x + self.y
    
    obj = MyClass(10, 20)
    
    members = inspect.getmembers(obj)
    print("MyClass 的成员:")
    for name, value in members:
        print(f"{name}: {value}")
    
    # 使用 predicate 过滤,只列出方法
    methods = inspect.getmembers(obj, inspect.isfunction)
    print("nMyClass 的方法:")
    for name, value in methods:
        print(f"{name}: {value}")

    输出结果:

    MyClass 的成员:
    __class__: <class '__main__.MyClass'>
    __delattr__: <method-wrapper '__delattr__' of MyClass object at 0x...>
    __dict__: {'x': 10, 'y': 20}
    __dir__: <built-in method __dir__ of MyClass object at 0x...>
    __doc__: None
    __eq__: <method-wrapper '__eq__' of MyClass object at 0x...>
    __format__: <method-wrapper '__format__' of MyClass object at 0x...>
    __ge__: <method-wrapper '__ge__' of MyClass object at 0x...>
    __getattribute__: <method-wrapper '__getattribute__' of MyClass object at 0x...>
    __gt__: <method-wrapper '__gt__' of MyClass object at 0x...>
    __hash__: <method-wrapper '__hash__' of MyClass object at 0x...>
    __init__: <bound method MyClass.__init__ of <__main__.MyClass object at 0x...>>
    __init_subclass__: <built-in method __init_subclass__ of type object at 0x...>
    __le__: <method-wrapper '__le__' of MyClass object at 0x...>
    __lt__: <method-wrapper '__lt__' of MyClass object at 0x...>
    __ne__: <method-wrapper '__ne__' of MyClass object at 0x...>
    __new__: <built-in method __new__ of type object at 0x...>
    __reduce__: <method-wrapper '__reduce__' of MyClass object at 0x...>
    __reduce_ex__: <method-wrapper '__reduce_ex__' of MyClass object at 0x...>
    __repr__: <method-wrapper '__repr__' of MyClass object at 0x...>
    __setattr__: <method-wrapper '__setattr__' of MyClass object at 0x...>
    __sizeof__: <built-in method __sizeof__ of MyClass object at 0x...>
    __str__: <method-wrapper '__str__' of MyClass object at 0x...>
    __subclasshook__: <built-in method __subclasshook__ of type object at 0x...>
    add: <bound method MyClass.add of <__main__.MyClass object at 0x...>>
    x: 10
    y: 20
    
    MyClass 的方法:
    add: <bound method MyClass.add of <__main__.MyClass object at 0x...>>

    可以看到,getmembers 列出了对象的所有成员,包括特殊方法(__init____str__ 等)、自定义方法(add)和属性(xy)。 我们还使用 inspect.isfunction 作为 predicate,只列出了方法。

  2. inspect.ismodule(object)inspect.isclass(object)inspect.isfunction(object)inspect.ismethod(object):判断对象类型

    这些函数用于判断对象的类型,返回 TrueFalse

    import inspect
    import math
    
    print(f"math 是模块吗? {inspect.ismodule(math)}")
    print(f"MyClass 是类吗? {inspect.isclass(MyClass)}")
    print(f"MyClass.add 是方法吗? {inspect.ismethod(obj.add)}") # 需要实例化对象
    print(f"math.sqrt 是函数吗? {inspect.isfunction(math.sqrt)}")

    输出结果:

    math 是模块吗? True
    MyClass 是类吗? True
    MyClass.add 是方法吗? True
    math.sqrt 是函数吗? True

    这些函数在配合 getmembers 使用时非常有用,可以让你根据类型过滤成员。

  3. inspect.getsource(object):获取源代码

    这个函数可以获取对象的源代码(如果可用的话)。

    import inspect
    
    def my_function(a, b):
        """
        这是一个示例函数。
        """
        return a + b
    
    source_code = inspect.getsource(my_function)
    print(source_code)

    输出结果:

    def my_function(a, b):
        """
        这是一个示例函数。
        """
        return a + b

    注意:getsource 只能获取用 Python 编写的对象的源代码。对于 C 扩展模块,或者动态生成的代码,它可能无法工作。

  4. inspect.getfile(object):获取定义对象的文件名

    这个函数可以获取定义对象的文件名。

    import inspect
    import math
    
    print(f"my_function 定义在: {inspect.getfile(my_function)}")
    print(f"math.sqrt 定义在: {inspect.getfile(math.sqrt)}") # 内置函数,找不到具体文件

    输出结果:

    my_function 定义在: /path/to/your/file.py  # 你的文件路径
    math.sqrt 定义在: /usr/lib/python3.x/lib-dynload/math.cpython-3x-x86_64-linux-gnu.so # 一个动态链接库

    对于内置函数或 C 扩展模块,getfile 可能会返回一些奇怪的结果,或者抛出异常。

  5. inspect.getmodule(object):获取对象所属的模块

    这个函数可以获取对象所属的模块。

    import inspect
    import math
    
    print(f"my_function 属于模块: {inspect.getmodule(my_function)}")
    print(f"math.sqrt 属于模块: {inspect.getmodule(math.sqrt)}")

    输出结果:

    my_function 属于模块: <module '__main__' from '/path/to/your/file.py'>
    math.sqrt 属于模块: <module 'math' (built-in)>
  6. inspect.signature(callable):获取函数签名

    这个函数可以获取函数或方法的签名,包括参数的名称、类型、默认值等信息。

    import inspect
    
    def my_function(a: int, b: str = "hello") -> float:
        """
        这是一个示例函数。
        """
        return float(a)
    
    sig = inspect.signature(my_function)
    print(sig)
    
    for param in sig.parameters.values():
        print(f"  参数名:{param.name}")
        print(f"  参数类型:{param.annotation}")
        print(f"  默认值:{param.default}")
        print(f"  参数种类:{param.kind}")

    输出结果:

    (a: int, b: str = 'hello') -> float
      参数名:a
      参数类型:<class 'int'>
      默认值:<class 'inspect._empty'>
      参数种类:POSITIONAL_OR_KEYWORD
      参数名:b
      参数类型:<class 'str'>
      默认值:hello
      参数种类:POSITIONAL_OR_KEYWORD

    signature 函数返回一个 Signature 对象,你可以通过它来访问参数的详细信息。 param.kind 表示参数的种类,常见的有:

    • POSITIONAL_OR_KEYWORD:可以通过位置或关键字传递的参数。
    • VAR_POSITIONAL:可变位置参数(*args)。
    • VAR_KEYWORD:可变关键字参数(**kwargs)。
    • KEYWORD_ONLY:只能通过关键字传递的参数。
    • POSITIONAL_ONLY:只能通过位置传递的参数(python3.8新增特性)。

第二部分:inspect 的高级用法

掌握了核心函数之后,我们来看看 inspect 的一些高级用法。

  1. 获取类的继承关系:inspect.getmro(cls)

    getmro 函数可以获取类的 MRO (Method Resolution Order),也就是方法解析顺序。MRO 决定了当一个类继承自多个父类时,如何查找方法。

    import inspect
    
    class A:
        def hello(self):
            print("Hello from A")
    
    class B(A):
        def hello(self):
            print("Hello from B")
    
    class C(A):
        pass
    
    class D(B, C):
        pass
    
    mro = inspect.getmro(D)
    print("D 的 MRO:")
    for cls in mro:
        print(cls)

    输出结果:

    D 的 MRO:
    <class '__main__.D'>
    <class '__main__.B'>
    <class '__main__.C'>
    <class '__main__.A'>
    <class 'object'>

    MRO 的顺序很重要,它决定了方法查找的顺序。在上面的例子中,如果 D 的实例调用 hello 方法,会先在 D 中查找,然后是 B,然后是 C,最后是 A

  2. 获取堆栈信息:inspect.stack()inspect.currentframe()

    inspect.stack() 可以获取当前线程的调用堆栈信息,inspect.currentframe() 可以获取当前帧对象。 这些函数在调试程序时非常有用,可以让你了解函数的调用关系。

    import inspect
    
    def func_a():
        func_b()
    
    def func_b():
        stack = inspect.stack()
        print("调用堆栈:")
        for frame_info in stack:
            print(f"  文件名:{frame_info.filename}")
            print(f"  行号:{frame_info.lineno}")
            print(f"  函数名:{frame_info.function}")
    
    func_a()

    输出结果:

    调用堆栈:
      文件名:/path/to/your/file.py
      行号:10
      函数名:func_b
      文件名:/path/to/your/file.py
      行号:7
      函数名:func_a
      文件名:/path/to/your/file.py
      行号:13
      函数名:<module>

    可以看到,调用堆栈按照调用的顺序排列,从当前函数(func_b)开始,一直到最顶层的模块。

  3. 动态地修改对象:setattr()delattr()

    虽然 inspect 的主要功能是获取元信息,但它也可以配合 setattr()delattr() 函数,动态地修改对象的属性。 注意:这种做法可能会破坏代码的结构,应该谨慎使用。

    import inspect
    
    class MyClass:
        def __init__(self, x):
            self.x = x
    
    obj = MyClass(10)
    print(f"obj.x 的值:{obj.x}")
    
    # 动态地添加属性
    setattr(obj, 'y', 20)
    print(f"obj.y 的值:{obj.y}")
    
    # 动态地删除属性
    delattr(obj, 'x')
    try:
        print(f"obj.x 的值:{obj.x}")  # 会抛出 AttributeError 异常
    except AttributeError as e:
        print(e)

    输出结果:

    obj.x 的值:10
    obj.y 的值:20
    'MyClass' object has no attribute 'x'

第三部分:inspect 的实际应用场景

inspect 模块在很多场景下都非常有用。

  1. 调试器

    调试器可以使用 inspect 获取变量的值、函数的调用堆栈等信息,帮助开发者定位错误。

  2. 代码分析工具

    代码分析工具可以使用 inspect 获取代码的结构、依赖关系等信息,进行静态分析。

  3. 单元测试

    单元测试可以使用 inspect 动态地创建测试用例、检查函数的参数等。

  4. 代码生成器

    代码生成器可以使用 inspect 获取类的结构信息,自动生成代码。例如,可以根据数据库表的结构,自动生成 ORM 模型的代码。

  5. 框架和库

    许多 Python 框架和库都使用 inspect 来实现一些高级功能。例如,Flask 框架使用 inspect 来自动注册路由,pytest 框架使用 inspect 来收集测试用例。

表格总结:inspect 常用函数一览

函数名 描述
inspect.getmembers() 列出对象的成员(属性、方法等)。
inspect.ismodule() 判断对象是否为模块。
inspect.isclass() 判断对象是否为类。
inspect.isfunction() 判断对象是否为函数。
inspect.ismethod() 判断对象是否为方法。
inspect.getsource() 获取对象的源代码。
inspect.getfile() 获取定义对象的文件名。
inspect.getmodule() 获取对象所属的模块。
inspect.signature() 获取函数签名,包括参数的名称、类型、默认值等信息。
inspect.getmro() 获取类的 MRO (Method Resolution Order)。
inspect.stack() 获取当前线程的调用堆栈信息。
inspect.currentframe() 获取当前帧对象。
inspect.getdoc() 获取对象的文档字符串(docstring)。
inspect.cleandoc() 清理文档字符串,去除缩进和空行。
inspect.formatargspec() 格式化函数的参数列表(已弃用,建议使用 inspect.signature())。
inspect.formatargvalues() 格式化函数的参数值(已弃用,建议使用 inspect.signature())。

结论:成为代码侦探

inspect 模块是 Python 中一个非常强大的工具,可以让你深入了解代码的内部结构和行为。掌握了 inspect,你就可以像一个真正的代码侦探一样,轻松地分析代码、调试程序、甚至动态地生成代码。

希望今天的讲座对大家有所帮助!记住,inspect 是你的代码 X 光机,有了它,没有什么代码是你看不透的! 谢谢大家!

发表回复

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