了解使基于 class 的装饰器支持实例方法的技术
Understanding a technique to make class-based decorators support instance methods
我最近在 Python 装饰器库的 memoized
装饰器中发现了一项技术,它允许它支持实例方法:
import collections
import functools
class memoized(object):
'''Decorator. Caches a function's return value each time it is called.
If called later with the same arguments, the cached value is returned
(not reevaluated).
'''
def __init__(self, func):
self.func = func
self.cache = {}
def __call__(self, *args):
if not isinstance(args, collections.Hashable):
# uncacheable. a list, for instance.
# better to not cache than blow up.
return self.func(*args)
if args in self.cache:
return self.cache[args]
else:
value = self.func(*args)
self.cache[args] = value
return value
def __repr__(self):
'''Return the function's docstring.'''
return self.func.__doc__
def __get__(self, obj, objtype):
'''Support instance methods.'''
return functools.partial(self.__call__, obj)
__get__
方法是,如文档字符串中所述,其中 'magic happens' 使装饰器支持实例方法。以下是一些表明它有效的测试:
import pytest
def test_memoized_function():
@memoized
def fibonacci(n):
"Return the nth fibonacci number."
if n in (0, 1):
return n
return fibonacci(n-1) + fibonacci(n-2)
assert fibonacci(12) == 144
def test_memoized_instance_method():
class Dummy(object):
@memoized
def fibonacci(self, n):
"Return the nth fibonacci number."
if n in (0, 1):
return n
return self.fibonacci(n-1) + self.fibonacci(n-2)
assert Dummy().fibonacci(12) == 144
if __name__ == "__main__":
pytest.main([__file__])
我想了解的是:这项技术究竟是如何工作的?它似乎非常普遍地适用于基于 class 的装饰器,我在对 Is it possible to numpy.vectorize an instance method? 的回答中应用了它。
到目前为止,我已经通过注释掉 __get__
方法并在 else
子句之后放入调试器来对此进行调查。 self.func
似乎是这样的,每当您尝试使用数字作为输入调用它时,它都会引发 TypeError
:
> /Users/kurtpeek/Documents/Scratch/memoize_fibonacci.py(24)__call__()
23 import ipdb; ipdb.set_trace()
---> 24 value = self.func(*args)
25 self.cache[args] = value
ipdb> self.func
<function Dummy.fibonacci at 0x10426f7b8>
ipdb> self.func(0)
*** TypeError: fibonacci() missing 1 required positional argument: 'n'
正如我从 https://docs.python.org/3/reference/datamodel.html#object.get 中了解到的那样,定义您自己的 __get__
方法会以某种方式覆盖您(在本例中)调用 self.func
时发生的事情,但我正在努力将这个例子的抽象文档。谁能一步步解释这个?
据我所知,当你使用描述符来修饰一个实例方法(实际上是一个属性)时,它定义了如何set
、get
和delete
这个属性。有个ref.
因此在您的示例中,memoized
的 __get__
定义了如何获取属性 fibonacci
。在 __get__
中,它将 obj
传递给 self.__call__
,其中 obj
是实例。而支持实例方法的关键是填写argument self
.
所以流程是:
假设 Dummy
有一个实例 dummy
。当您访问 dummy
的属性 fibonacci
时,因为它已被 memoized
修饰。属性 fibonacci
的值由 memoized.__get__
返回。 __get__
接受两个参数,一个是调用实例(这里是 dummy
),另一个是它的类型。 memoized.__get__
将实例填充到 self.__call__
以便在原始方法 fibonacci
.
中填充 self
参数
为了更好地理解描述符,有一个 example:
class RevealAccess(object):
"""A data descriptor that sets and returns values
normally and prints a message logging their access.
"""
def __init__(self, initval=None, name='var'):
self.val = initval
self.name = name
def __get__(self, obj, objtype):
print('Retrieving', self.name)
return self.val
def __set__(self, obj, val):
print('Updating', self.name)
self.val = val
>>> class MyClass(object):
... x = RevealAccess(10, 'var "x"')
... y = 5
...
>>> m = MyClass()
>>> m.x
Retrieving var "x"
10
>>> m.x = 20
Updating var "x"
>>> m.x
Retrieving var "x"
20
>>> m.y
5
我最近在 Python 装饰器库的 memoized
装饰器中发现了一项技术,它允许它支持实例方法:
import collections
import functools
class memoized(object):
'''Decorator. Caches a function's return value each time it is called.
If called later with the same arguments, the cached value is returned
(not reevaluated).
'''
def __init__(self, func):
self.func = func
self.cache = {}
def __call__(self, *args):
if not isinstance(args, collections.Hashable):
# uncacheable. a list, for instance.
# better to not cache than blow up.
return self.func(*args)
if args in self.cache:
return self.cache[args]
else:
value = self.func(*args)
self.cache[args] = value
return value
def __repr__(self):
'''Return the function's docstring.'''
return self.func.__doc__
def __get__(self, obj, objtype):
'''Support instance methods.'''
return functools.partial(self.__call__, obj)
__get__
方法是,如文档字符串中所述,其中 'magic happens' 使装饰器支持实例方法。以下是一些表明它有效的测试:
import pytest
def test_memoized_function():
@memoized
def fibonacci(n):
"Return the nth fibonacci number."
if n in (0, 1):
return n
return fibonacci(n-1) + fibonacci(n-2)
assert fibonacci(12) == 144
def test_memoized_instance_method():
class Dummy(object):
@memoized
def fibonacci(self, n):
"Return the nth fibonacci number."
if n in (0, 1):
return n
return self.fibonacci(n-1) + self.fibonacci(n-2)
assert Dummy().fibonacci(12) == 144
if __name__ == "__main__":
pytest.main([__file__])
我想了解的是:这项技术究竟是如何工作的?它似乎非常普遍地适用于基于 class 的装饰器,我在对 Is it possible to numpy.vectorize an instance method? 的回答中应用了它。
到目前为止,我已经通过注释掉 __get__
方法并在 else
子句之后放入调试器来对此进行调查。 self.func
似乎是这样的,每当您尝试使用数字作为输入调用它时,它都会引发 TypeError
:
> /Users/kurtpeek/Documents/Scratch/memoize_fibonacci.py(24)__call__()
23 import ipdb; ipdb.set_trace()
---> 24 value = self.func(*args)
25 self.cache[args] = value
ipdb> self.func
<function Dummy.fibonacci at 0x10426f7b8>
ipdb> self.func(0)
*** TypeError: fibonacci() missing 1 required positional argument: 'n'
正如我从 https://docs.python.org/3/reference/datamodel.html#object.get 中了解到的那样,定义您自己的 __get__
方法会以某种方式覆盖您(在本例中)调用 self.func
时发生的事情,但我正在努力将这个例子的抽象文档。谁能一步步解释这个?
据我所知,当你使用描述符来修饰一个实例方法(实际上是一个属性)时,它定义了如何set
、get
和delete
这个属性。有个ref.
因此在您的示例中,memoized
的 __get__
定义了如何获取属性 fibonacci
。在 __get__
中,它将 obj
传递给 self.__call__
,其中 obj
是实例。而支持实例方法的关键是填写argument self
.
所以流程是:
假设 Dummy
有一个实例 dummy
。当您访问 dummy
的属性 fibonacci
时,因为它已被 memoized
修饰。属性 fibonacci
的值由 memoized.__get__
返回。 __get__
接受两个参数,一个是调用实例(这里是 dummy
),另一个是它的类型。 memoized.__get__
将实例填充到 self.__call__
以便在原始方法 fibonacci
.
self
参数
为了更好地理解描述符,有一个 example:
class RevealAccess(object):
"""A data descriptor that sets and returns values
normally and prints a message logging their access.
"""
def __init__(self, initval=None, name='var'):
self.val = initval
self.name = name
def __get__(self, obj, objtype):
print('Retrieving', self.name)
return self.val
def __set__(self, obj, val):
print('Updating', self.name)
self.val = val
>>> class MyClass(object):
... x = RevealAccess(10, 'var "x"')
... y = 5
...
>>> m = MyClass()
>>> m.x
Retrieving var "x"
10
>>> m.x = 20
Updating var "x"
>>> m.x
Retrieving var "x"
20
>>> m.y
5