【发布时间】:2019-09-13 23:41:39
【问题描述】:
我使用装饰器通过 lru_cache 将 memoization 扩展到本身不可散列的对象的方法(遵循stackoverflow.com/questions/33672412/python-functools-lru-cache-with-class-methods-release-object)。 此记忆在 python 3.6 上运行良好,但在 python 3.7 上显示意外行为。
观察到的行为: 如果使用关键字参数调用 memoized 方法,则 memoization 在两个 python 版本上都可以正常工作。如果在没有关键字 arg 语法的情况下调用它,它适用于 3.6,但不适用于 3.7。
==> 什么可能导致不同的行为?
下面的代码示例显示了一个重现该行为的最小示例。
test_memoization_kwarg_call 适用于 python 3.6 和 3.7。
test_memoization_arg_call 在 python 3.6 上通过,但在 3.7 上失败。
import random
import weakref
from functools import lru_cache
def memoize_method(func):
# From stackoverflow.com/questions/33672412/python-functools-lru-cache-with-class-methods-release-object
def wrapped_func(self, *args, **kwargs):
self_weak = weakref.ref(self)
@lru_cache()
def cached_method(*args_, **kwargs_):
return func(self_weak(), *args_, **kwargs_)
setattr(self, func.__name__, cached_method)
print(args)
print(kwargs)
return cached_method(*args, **kwargs)
return wrapped_func
class MyClass:
@memoize_method
def randint(self, param):
return random.randint(0, int(1E9))
def test_memoization_kwarg_call():
obj = MyClass()
assert obj.randint(param=1) == obj.randint(param=1)
assert obj.randint(1) == obj.randint(1)
def test_memoization_arg_call():
obj = MyClass()
assert obj.randint(1) == obj.randint(1)
请注意,奇怪的是,assert obj.randint(1) == obj.randint(1) 行在 python 3.6 中使用时不会导致 test_memoization_kwarg_call 中的测试失败,但在 test_memoization_arg_call 中的 python 3.7 中会失败。
Python 版本:分别为 3.6.8 和 3.7.3。
更多信息
user2357112 建议检查import dis; dis.dis(test_memoization_arg_call)。
在 python 3.6 上,这给出了
36 0 LOAD_GLOBAL 0 (MyClass)
2 CALL_FUNCTION 0
4 STORE_FAST 0 (obj)
37 6 LOAD_FAST 0 (obj)
8 LOAD_ATTR 1 (randint)
10 LOAD_CONST 1 (1)
12 CALL_FUNCTION 1
14 LOAD_FAST 0 (obj)
16 LOAD_ATTR 1 (randint)
18 LOAD_CONST 1 (1)
20 CALL_FUNCTION 1
22 COMPARE_OP 2 (==)
24 POP_JUMP_IF_TRUE 30
26 LOAD_GLOBAL 2 (AssertionError)
28 RAISE_VARARGS 1
>> 30 LOAD_CONST 0 (None)
32 RETURN_VALUE
在 python 3.7 上,这给出了
36 0 LOAD_GLOBAL 0 (MyClass)
2 CALL_FUNCTION 0
4 STORE_FAST 0 (obj)
37 6 LOAD_FAST 0 (obj)
8 LOAD_METHOD 1 (randint)
10 LOAD_CONST 1 (1)
12 CALL_METHOD 1
14 LOAD_FAST 0 (obj)
16 LOAD_METHOD 1 (randint)
18 LOAD_CONST 1 (1)
20 CALL_METHOD 1
22 COMPARE_OP 2 (==)
24 POP_JUMP_IF_TRUE 30
26 LOAD_GLOBAL 2 (AssertionError)
28 RAISE_VARARGS 1
>> 30 LOAD_CONST 0 (None)
32 RETURN_VALUE
不同之处在于,在 3.6 上调用缓存的 randint 方法会产生 LOAD_ATTR, LOAD_CONST, CALL_FUNCTION,而在 3.7 上会产生 LOAD_METHOD, LOAD_CONST, CALL_METHOD。这可以解释行为上的差异,但我不了解 CPython 的内部结构(?)来理解它。有什么想法吗?
【问题讨论】:
-
你能在 Python 3 上显示
import dis; dis.dis(test_memoization_arg_call)的输出吗?另外,你是如何运行这段代码的? -
我无法重现您在 Python 3.7.0 上描述的行为。
-
我也发现这两种情况都适用于 3.6.4 和 3.7.2。
-
@user2357112 感谢您推荐
dis.dis。我在上面添加了输出。在 3.6 上,对缓存的 randint 方法的调用会产生 LOAD_ATTR、LOAD_CONST、CALL_FUNCTION,而在 3.7 上会产生 LOAD_METHOD、LOAD_CONST、CALL_METHOD。这可以解释行为上的差异,但我不了解 CPython 的内部结构(?)来理解它。有什么想法吗? -
这是一个已修复的错误,应该作为下一个版本的一部分发布。 Raymond Hettinger 对 Python 问题跟踪器中发生的事情进行了很好的描述:bugs.python.org/issue36650。
标签: python python-3.x python-3.6 python-3.7