19.5. inspect — 检阅实时对象

未匹配的标注

目的: inspect 模块提供对实时对象及其源代码进行内省的函数。

inspect 模块提供实时了解对象的函数,包括模块、类、实例、函数和方法。 此模块中的函数可用于检索函数的原始源代码,查看堆栈上方法的参数,并提取有助于生成源代码库文档的信息。

示例模块

本节的其余示例使用此示例文件 example.py

example.py

# 此注释首先出现
# 并跨越 2 行。

# 此注释不会出现在 getcomments() 的输出中。

"""示例文件用作 inspect 示例的基础。
"""

def module_level_function(arg1, arg2='default', *args, **kwargs):
    """该函数在模块中声明"""
    local_variable = arg1 * 2
    return local_variable

class A(object):
    """The A class."""

    def __init__(self, name):
        self.name = name

    def get_name(self):
        "Returns the name of the instance."
        return self.name

instance_of_a = A('sample_instance')

class B(A):
    """This is the B class.
    It is derived from A.
    """

    # 此方法不是 A 的一部分。
    def do_something(self):
        """Does some work"""

    def get_name(self):
        "Overrides version from A"
        return 'B(' + self.name + ')'

审查模块

第一种内省探测活动对象来了解模块。 使用 getmembers() 来发现对象的成员属性。 可能返回的成员类型取决于扫描的对象类型。 模块可以包含类和函数;类可以包含方法和属性;等等。

getmembers() 的参数是一个扫描对象(模块、类或实例)和一个可选的谓词函数,用于过滤返回的对象。 返回值是具有两个值的元组列表:成员的名称和成员的类型。 inspect 模块包括几个这样的谓词函数,其名称如 ismodule()isclass() 等。

inspect_getmembers_module.py

import inspect

import example

for name, data in inspect.getmembers(example):
    if name.startswith('__'):
        continue
    print('{} : {!r}'.format(name, data))

此示例打印了 example 模块的成员。 模块有几个私有属性,用作导入实现的一部分,以及一组 __builtins__ 。 在这个例子的输出中忽略了所有这些因为它们实际上不是模块的一部分而且列表很长。

$ python3 inspect_getmembers_module.py

A : <class 'example.A'>
B : <class 'example.B'>
instance_of_a : <example.A object at 0x1045a6978>
module_level_function : <function module_level_function at
0x1045be8c8>

predicate 参数可用于过滤返回的对象类型。

inspect_getmembers_module_class.py

import inspect

import example

for name, data in inspect.getmembers(example, inspect.isclass):
    print('{} : {!r}'.format(name, data))

现在输出中只包含类。

$ python3 inspect_getmembers_module_class.py

A : <class 'example.A'>
B : <class 'example.B'>

审查类

使用 getmembers() 以与模块相同的方式扫描类,尽管成员的类型不同。

inspect_getmembers_class.py

import inspect
from pprint import pprint

import example

pprint(inspect.getmembers(example.A), width=65)

由于未应用过滤,因此输出显示类的属性、方法、槽和其他成员。

$ python3 inspect_getmembers_class.py

[('__class__', <class 'type'>),
 ('__delattr__',
  <slot wrapper '__delattr__' of 'object' objects>),
 ('__dict__',
  mappingproxy({'__dict__': <attribute '__dict__' of 'A'
objects>,
                '__doc__': 'The A class.',
                '__init__': <function A.__init__ at
0x1045b3158>,
                '__module__': 'example',
                '__weakref__': <attribute '__weakref__' of 'A'
objects>,
                'get_name': <function A.get_name at
0x1045b31e0>})),
 ('__dir__', <method '__dir__' of 'object' objects>),
 ('__doc__', 'The A class.'),
 ('__eq__', <slot wrapper '__eq__' of 'object' objects>),
 ('__format__', <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 A.__init__ at 0x1045b3158>),
 ('__init_subclass__',
  <built-in method __init_subclass__ of type object at
0x101d12d58>),
 ('__le__', <slot wrapper '__le__' of 'object' objects>),
 ('__lt__', <slot wrapper '__lt__' of 'object' objects>),
 ('__module__', 'example'),
 ('__ne__', <slot wrapper '__ne__' of 'object' objects>),
 ('__new__',
  <built-in method __new__ of type object at 0x100996700>),
 ('__reduce__', <method '__reduce__' of 'object' objects>),
 ('__reduce_ex__', <method '__reduce_ex__' of 'object'
objects>),
 ('__repr__', <slot wrapper '__repr__' of 'object' objects>),
 ('__setattr__',
  <slot wrapper '__setattr__' of 'object' objects>),
 ('__sizeof__', <method '__sizeof__' of 'object' objects>),
 ('__str__', <slot wrapper '__str__' of 'object' objects>),
 ('__subclasshook__',
  <built-in method __subclasshook__ of type object at
0x101d12d58>),
 ('__weakref__', <attribute '__weakref__' of 'A' objects>),
 ('get_name', <function A.get_name at 0x1045b31e0>)]

要查找类的方法,请使用 isfunction() 谓词。 ismethod() 谓词只识别实例的绑定方法。

inspect_getmembers_class_methods.py

import inspect
from pprint import pprint

import example

pprint(inspect.getmembers(example.A, inspect.isfunction))

现在只返回未绑定的方法。

$ python3 inspect_getmembers_class_methods.py

[('__init__', <function A.__init__ at 0x1045b2158>),
 ('get_name', <function A.get_name at 0x1045b21e0>)]

B 的输出包括 get_name() 的覆写以及新的方法,以及在 A 中继承实现的 __init __() 方法。

inspect_getmembers_class_methods_b.py

import inspect
from pprint import pprint

import example

pprint(inspect.getmembers(example.B, inspect.isfunction))

A 继承的方法,例如 __init __() ,被识别为 B 的方法。

$ python3 inspect_getmembers_class_methods_b.py

[('__init__', <function A.__init__ at 0x103dc5158>),
 ('do_something', <function B.do_something at 0x103dc5268>),
 ('get_name', <function B.get_name at 0x103dc52f0>)]

审查实例

内省实例的工作方式与其他对象相同。

inspect_getmembers_instance.py

import inspect
from pprint import pprint

import example

a = example.A(name='inspect_getmembers')
pprint(inspect.getmembers(a, inspect.ismethod))

谓词 ismethod() 在示例实例中识别来自 A 的两个绑定方法。

$ python3 inspect_getmembers_instance.py

[('__init__', <bound method A.__init__ of <example.A object at 0
x101d9c0f0>>),
 ('get_name', <bound method A.get_name of <example.A object at 0
x101d9c0f0>>)]

文档字符串

一个对象的文档字符串可以通过 getdoc()获得。 返回值是制表符之间的格式缩进的 __doc__ 属性。

inspect_getdoc.py

import inspect
import example

print('B.__doc__:')
print(example.B.__doc__)
print()
print('getdoc(B):')
print(inspect.getdoc(example.B))

当被直接调用,文档第二行缩进, 但通过getdoc()移到了最左边界。

$ python3 inspect_getdoc.py

B.__doc__:
This is the B class.
    It is derived from A.

getdoc(B):
This is the B class.
It is derived from A.

在实际文档字符串中,如果源码可被查询,执行对象从源码查看注释是可行的 。  getcomments() 函数在命令执行前就从对代码开始找到执行命令行。

inspect_getcomments_method.py

import inspect
import example

print(inspect.getcomments(example.B.do_something))

文档返回包括去掉所有空白前缀的命令行。

$ python3 inspect_getcomments_method.py

# This method is not part of A.

当一个模块被传递给 getcomments(), 返回值通常是该模块第一个指令。

inspect_getcomments_module.py

import inspect
import example

print(inspect.getcomments(example))

示例文件中的联系字符行包括一个简单的执行命令,但一旦空白行出现,这个执行终止。

$ python3 inspect_getcomments_module.py

# This comment appears first
# and spans 2 lines.

检索来源

如果模块包含可用的 .py 文件,则可以使用 getsource()getsourcelines() 来检索类或方法的原始源代码。

inspect_getsource_class.py

import inspect
import example

print(inspect.getsource(example.A))

传入类时,该类的所有方法都包含在输出中。

$ python3 inspect_getsource_class.py

class A(object):
    """The A class."""

    def __init__(self, name):
        self.name = name

    def get_name(self):
        "Returns the name of the instance."
        return self.name

要检索单个方法的源,请将方法引用传递给 getsource()

inspect_getsource_method.py

import inspect
import example

print(inspect.getsource(example.A.get_name))

在这种情况下保留原始缩进级别。

$ python3 inspect_getsource_method.py

    def get_name(self):
        "Returns the name of the instance."
        return self.name

使用getsourcelines() 而不是 getsource() 来检索分割成单个字符串的源代码行。

inspect_getsourcelines_method.py

import inspect
import pprint
import example

pprint.pprint(inspect.getsourcelines(example.A.get_name))

getsourcelines() 的返回值是一个 tuple ,它包含一个字符串列表(源文件中的行),以及源出现在文件中的起始行号。

$ python3 inspect_getsourcelines_method.py

(['    def get_name(self):\n',
  '        "Returns the name of the instance."\n',
  '        return self.name\n'],
 23)

如果源文件不可用, getsource()getsourcelines() 引发 IOError

方法和函数签名

除了函数或方法的文档之外,还可以要求对可调用的参数进行完整的规范,包括默认值。 signature() 函数返回一个 Signature 实例,其中包含有关该函数参数的信息。

inspect_signature_function.py

import inspect
import example

sig = inspect.signature(example.module_level_function)
print('module_level_function{}'.format(sig))

print('\nParameter details:')
for name, param in sig.parameters.items():
    if param.kind == inspect.Parameter.POSITIONAL_ONLY:
        print('  {} (positional-only)'.format(name))
    elif param.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD:
        if param.default != inspect.Parameter.empty:
            print('  {}={!r}'.format(name, param.default))
        else:
            print('  {}'.format(name))
    elif param.kind == inspect.Parameter.VAR_POSITIONAL:
        print('  *{}'.format(name))
    elif param.kind == inspect.Parameter.KEYWORD_ONLY:
        if param.default != inspect.Parameter.empty:
            print('  {}={!r} (keyword-only)'.format(
                name, param.default))
        else:
            print('  {} (keyword-only)'.format(name))
    elif param.kind == inspect.Parameter.VAR_KEYWORD:
        print('  **{}'.format(name))

函数参数可通过 Signatureparameters 属性获得。 parameters 是一个有序的字典,将参数名称映射到描述参数的 Parameter 实例。 在这个例子中,函数的第一个参数 arg1 没有默认值,而 arg2 则有。

$ python3 inspect_signature_function.py

module_level_function(arg1, arg2='default', *args, **kwargs)

Parameter details:
  arg1
  arg2='default'
  *args
  **kwargs

函数的 Signature 可以被装饰器或其他函数用来验证输入,提供不同的默认值等。编写一个适当的通用和可重用的验证装饰器有一个特殊的挑战,因为将传入的参数与接受命名和位置参数组合的函数的名称进行匹配可能很复杂。 bind()bind_partial() 方法提供了处理映射所必需的逻辑。 它们返回一个 BoundArguments 实例,该实例填充了与指定函数的参数名称相关联的参数。

inspect_signature_bind.py

import inspect
import example

sig = inspect.signature(example.module_level_function)

bound = sig.bind(
    'this is arg1',
    'this is arg2',
    'this is an extra positional argument',
    extra_named_arg='value',
)

print('Arguments:')
for name, value in bound.arguments.items():
    print('{} = {!r}'.format(name, value))

print('\nCalling:')
print(example.module_level_function(*bound.args, **bound.kwargs))

BoundArguments 实例具有属性 argskwargs ,可用于使用语法调用函数,将元组和字典扩展到堆栈作为参数。

$ python3 inspect_signature_bind.py

Arguments:
arg1 = 'this is arg1'
arg2 = 'this is arg2'
args = ('this is an extra positional argument',)
kwargs = {'extra_named_arg': 'value'}

Calling:
this is arg1this is arg1

如果只有一些参数可用, bind_partial() 仍然会创建一个 BoundArguments 实例。 在添加其余参数之前,它可能无法完全使用。

inspect_signature_bind_partial.py

import inspect
import example

sig = inspect.signature(example.module_level_function)

partial = sig.bind_partial(
    'this is arg1',
)

print('Without defaults:')
for name, value in partial.arguments.items():
    print('{} = {!r}'.format(name, value))

print('\nWith defaults:')
partial.apply_defaults()
for name, value in partial.arguments.items():
    print('{} = {!r}'.format(name, value))

apply_defaults() 将添加参数默认值中的任何值。

$ python3 inspect_signature_bind_partial.py

Without defaults:
arg1 = 'this is arg1'

With defaults:
arg1 = 'this is arg1'
arg2 = 'default'
args = ()
kwargs = {}

类层次结构

inspect 包括两种直接使用类层次结构的方法。 第一个是 getclasstree() ,它根据给定的类及其基类创建一个类似树的数据结构。 返回的列表中的每个元素都是带有类及其基类的元组,或者是包含子类元组的另一个列表。

inspect_getclasstree.py

import inspect
import example

class C(example.B):
    pass

class D(C, example.A):
    pass

def print_class_tree(tree, indent=-1):
    if isinstance(tree, list):
        for node in tree:
            print_class_tree(node, indent + 1)
    else:
        print('  ' * indent, tree[0].__name__)
    return

if __name__ == '__main__':
    print('A, B, C, D:')
    print_class_tree(inspect.getclasstree(
        [example.A, example.B, C, D])
    )

这个例子的输出是 ABCD 类的继承树。 D 出现两次,因为它继承了 CA

$ python3 inspect_getclasstree.py

A, B, C, D:
 object
   A
     D
     B
       C
         D

如果调用 getclasstree() 并将 unique 设置为 true 值,则输出不同。

inspect_getclasstree_unique.py

import inspect
import example
from inspect_getclasstree import *

print_class_tree(inspect.getclasstree(
    [example.A, example.B, C, D],
    unique=True,
))

这次, D 只出现在输出中一次:

$ python3 inspect_getclasstree_unique.py

 object
   A
     B
       C
         D

方法解析顺序

使用类层次结构的另一个函数是 getmro() ,当使用 Method Resolution Order ( MRO )解析可能从基类继承的属性时,它按照应该扫描的顺序返回类的 tuple 。 序列中的每个类只出现一次。

inspect_getmro.py

import inspect
import example

class C(object):
    pass

class C_First(C, example.B):
    pass

class B_First(example.B, C):
    pass

print('B_First:')
for c in inspect.getmro(B_First):
    print('  {}'.format(c.__name__))
print()
print('C_First:')
for c in inspect.getmro(C_First):
    print('  {}'.format(c.__name__))

此输出演示了 MRO 搜索的「深度优先」特性。 对于 B_FirstA 也出现在搜索顺序中的 C 之前,因为 B 来自 A

$ python3 inspect_getmro.py

B_First:
  B_First
  B
  A
  C
  object

C_First:
  C_First
  C
  B
  A
  object

堆栈和帧

除了代码对象的内省之外, inspect 还包括在执行程序时检查运行时环境的函数。 这些函数中的大多数都与调用堆栈一起使用,并在调用帧上运行。 Frame 对象包含当前执行的上下文,包括对正在运行的代码的引用,正在执行的操作以及本地和全局变量的值。 通常,此类信息用于在引发异常时构建回溯。 它对于日志记录或调试程序也很有用,因为可以查询堆栈帧以发现传递给函数的参数值。

currentframe() 返回堆栈顶部的帧(对于当前函数)。

inspect_currentframe.py

import inspect
import pprint

def recurse(limit, keyword='default', *, kwonly='must be named'):
    local_variable = '.' * limit
    keyword = 'changed value of argument'
    frame = inspect.currentframe()
    print('line {} of {}'.format(frame.f_lineno,
                                 frame.f_code.co_filename))
    print('locals:')
    pprint.pprint(frame.f_locals)
    print()
    if limit <= 0:
        return
    recurse(limit - 1)
    return local_variable

if __name__ == '__main__':
    recurse(2)

recurse() 参数的值包含在帧的局部变量字典中。

$ python3 inspect_currentframe.py

line 14 of inspect_currentframe.py
locals:
{'frame': <frame object at 0x10458d408>,
 'keyword': 'changed value of argument',
 'kwonly': 'must be named',
 'limit': 2,
 'local_variable': '..'}

line 14 of inspect_currentframe.py
locals:
{'frame': <frame object at 0x101b1ba18>,
 'keyword': 'changed value of argument',
 'kwonly': 'must be named',
 'limit': 1,
 'local_variable': '.'}

line 14 of inspect_currentframe.py
locals:
{'frame': <frame object at 0x101b2cdc8>,
 'keyword': 'changed value of argument',
 'kwonly': 'must be named',
 'limit': 0,
 'local_variable': ''}

使用 stack() ,还可以访问从当前帧到第一个调用者的所有堆栈帧。 此示例类似于前面所示的示例,除了它等到递归结束以打印堆栈信息。

inspect_stack.py

import inspect
import pprint

def show_stack():
    for level in inspect.stack():
        print('{}[{}]\n  -> {}'.format(
            level.frame.f_code.co_filename,
            level.lineno,
            level.code_context[level.index].strip(),
        ))
        pprint.pprint(level.frame.f_locals)
        print()

def recurse(limit):
    local_variable = '.' * limit
    if limit <= 0:
        show_stack()
        return
    recurse(limit - 1)
    return local_variable

if __name__ == '__main__':
    recurse(2)

输出的最后一部分代表主程序,在 recursive() 函数之外。

$ python3 inspect_stack.py

inspect_stack.py[11]
  -> for level in inspect.stack():
{'level': FrameInfo(frame=<frame object at 0x1045823f8>,
filename='inspect_stack.py', lineno=11, function='show_stack',
code_context=['    for level in inspect.stack():\n'], index=0)}

inspect_stack.py[24]
  -> show_stack()
{'limit': 0, 'local_variable': ''}

inspect_stack.py[26]
  -> recurse(limit - 1)
{'limit': 1, 'local_variable': '.'}

inspect_stack.py[26]
  -> recurse(limit - 1)
{'limit': 2, 'local_variable': '..'}

inspect_stack.py[31]
  -> recurse(2)
{'__annotations__': {},
 '__builtins__': <module 'builtins' (built-in)>,
 '__cached__': None,
 '__doc__': 'Inspecting the call stack.\n',
 '__file__': 'inspect_stack.py',
 '__loader__': <_frozen_importlib_external.SourceFileLoader
object at 0x101f9c080>,
 '__name__': '__main__',
 '__package__': None,
 '__spec__': None,
 'inspect': <module 'inspect' from
'.../lib/python3.6/inspect.py'>,
 'pprint': <module 'pprint' from '.../lib/python3.6/pprint.py'>,
 'recurse': <function recurse at 0x1045b9f28>,
 'show_stack': <function show_stack at 0x101f21e18>}

还有其他功能用于在不同的上下文中构建帧列表,例如在处理异常时。 有关的详细信息,请参阅 trace()getouterframes()getinnerframes() 的文档。

命令行接口

inspect 模块还包括一个命令行接口,用于获取有关对象的详细信息,而无需在单独的 Python 程序中写出调用。 输入是模块中的模块名称和可选对象。 默认输出是命名对象的源代码。 使用 --details 参数会打印元数据而不是源。

$ python3 -m inspect -d example

Target: example
Origin: .../example.py
Cached: .../__pycache__/example.cpython-36.pyc
Loader: <_frozen_importlib_external.SourceFileLoader object at 0
x103e16fd0>

$ python3 -m inspect -d example:A

Target: example:A
Origin: .../example.py
Cached: .../__pycache__/example.cpython-36.pyc
Line: 16

$ python3 -m inspect example:A.get_name

    def get_name(self):
        "Returns the name of the instance."
        return self.name

另请参阅

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://learnku.com/docs/pymotw/inspect-...

译文地址:https://learnku.com/docs/pymotw/inspect-...

上一篇 下一篇
讨论数量: 0
发起讨论 只看当前版本


暂无话题~