首页 > 解决方案 > 类装饰器中的 lambda 上的 inspect.getcode 返回 PY3 中的整个类

问题描述

我们终于从 Python 2.7.13 迁移到 Python 3.6.6,我发现 inspect.getcode 的一些奇怪行为与 Python 2.7.13 相比,在 Python 3.6.6 中的行为非常不同。

我们有许多具有装饰器的类,该装饰器定义是否允许此类的实例执行特定操作。此检查由管理这些装饰器的单独模块完成,而不是由类本身完成。为了避免意外更改,我们使用单元测试来记录装饰器内的代码,并将其与基准进行比较。代码用inspect.getcode() 记录。如果装饰器使用外部方法,那么 PY2 和 PY3 捕获的结果是相同的 - 方法的代码。但是,如果装饰器使用 lambda 表达式,结果就完全不同了:

我在 inspect.getsource() 中找不到任何可以解释这一点的记录差异。我在这里遗漏了什么还是这是一个错误?我知道有一些简单的解决方法,例如我可以检查返回的字符串并剪辑 lambda 下的所有内容 - 但我宁愿理解为什么会发生这种情况。

这是一些可以直接在 PY2 和 PY3 中运行的示例代码(需要六个)。这段代码是从头开始编写的以演示问题 - 请不要担心装饰器的实际实现或其他实现方式。

checks = dict()  # a mapping of class to check function

# the class decorator
def check(check_function):
    def wrap(wrapped):
        checks[wrapped] = check_function
        return wrapped
    return wrap

# example 1: check with lambda. This demonstrates the problem
@check(lambda cls: True)
class checked_with_lambda(object):
    def run(self):
        pass

# example 2: check with external helper. This works as expected
def my_checker(obj):
    return True

@check(my_checker)
class checked_with_function(object):
    def run(self):
        pass


# this is how the check would be used. This works fine
c = checked_with_lambda()
if checks[type(c)](c):
    c.run()

# this is what a unit test would do 
# (I have added the looks-like line to show that the check function is actually what I am expecting)
import six, inspect
for cls, check in six.iteritems(checks):
    print('Check for "{}":\n'
          'looks like: "{}"\n'
          'code: "{}"\n'.format(cls.__name__,
                                repr(check),
                                inspect.getsource(check)))

如果我使用 Python 2.7.13 运行此代码,则此结果符合预期:

Check for "checked_with_lambda":
looks like: "<function <lambda> at 0x0000025967FB2A58>"
code: "@check(lambda cls: True)
"

Check for "checked_with_function":
looks like: "<function my_checker at 0x0000025967FB2AC8>"
code: "def my_checker(obj):
    return True
"

在 PY3 中,这看起来完全不同 - 请注意第一个类的检查功能代码如何包含整个类代码:

Check for "checked_with_lambda":
looks like: "<function <lambda> at 0x000002A2B295BC80>"
code: "@check(lambda cls: True)
class checked_with_lambda(object):
    def run(self):
        pass
"

Check for "checked_with_function":
looks like: "<function my_checker at 0x000002A2B295BD08>"
code: "def my_checker(obj):
    return True
"

标签: pythonlambdadecoratorinspectsix

解决方案


AFAICT 这是 Python 3 中的故意更改。 inspect.getsource是基于行的,在 Python 3 中,inspect.BlockFinder帮助程序类检测 lambda 定义所在的行是装饰器并返回整个装饰对象。Python 2 没有这样做。你只需要解决这个问题。


推荐阅读