我们终于从Python 2.7.13迁移到Python 3.6.6,我发现了inspect.getcode的一些奇怪的行为,与Python 2.7.13相比,它在Python 3.6.6中的行为非常不同。
我们有许多类,它们有一个装饰器,用于定义是否允许此类的实例执行特定操作。此检查由管理这些装饰器的单独模块完成,而不是由类本身完成。 为了避免意外更改,我们使用单元测试来记录装饰器中的代码,并将其与基准进行比较。代码使用 inspect.getcode(( 记录。 如果装饰器使用外部方法,则 PY2 和 PY3 捕获的结果是相同的 - 方法的代码。 但是,如果装饰器使用 lambda 表达式,则结果会大不相同:
- PY2 返回整个装饰器,而不仅仅是 lambda 表达式,但这对于单元测试来说并不是真正的问题。
- 但是,PY3 返回装饰器和整个类 - 这是一个问题,因为现在类中的任何更改都会破坏此单元测试。
我在 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
"
AFAICT 这是 Python 3 中有意更改的内容。inspect.getsource
是基于行的,在 Python 3 中,inspect.BlockFinder
帮助程序类检测到 lambda 定义所在的行是一个装饰器,并返回整个装饰对象。 Python 2 没有这样做。 你只需要解决这个问题。