Я использую декоратор для расширения запоминания через lru_cache на методы объектов, которые сами по себе не могут быть хэшируемыми (следуя qaru.site/info/313334/...). Эта памятка прекрасно работает с питоном 3.6, но демонстрирует неожиданное поведение на питоне 3.7.
Наблюдаемое поведение: если метод memoized вызывается с аргументами ключевого слова, то меморизация отлично работает в обеих версиях 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/info/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)
не приводит к сбою теста в test_memoization_kwarg_call
при использовании в python 3.6, но завершается неудачей для python 3.7 внутри test_memoization_arg_call
.
Версии Python: 3.6.8 и 3.7.3 соответственно.
Дальнейшая информация
user2357112 предложил проверить import dis; dis.dis(test_memoization_arg_call)
import dis; dis.dis(test_memoization_arg_call)
. На питоне 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
На питоне 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 (?), Чтобы понять это. Есть идеи?