了解一种使基于班级装饰的技术支持实例方法的技术



我最近在Python Decorator库的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__方法是在DOC字符串中解释的,其中"魔术发生"以使装饰器支持实例方法。这是一些测试,表明它有效:

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__])

我要理解的是:这项技术如何准确起作用?它似乎通常适用于基于班级的装饰器,我在答案中将其应用于numpy。

到目前为止,我已经通过评论__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,但我正在努力将抽象文档与此示例联系起来。谁能逐步解释此?

据我所知,当您使用描述符来装饰实例方法(实际上是属性(时,它定义了如何 setgetdelete此属性的行为。有一个参考

因此,在您的示例中,memoized__get__定义了如何获取属性fibonacci。在__get__中,它将obj传递给self.__call__哪个obj是实例。支持实例方法的关键是填写参数self

这样的过程是:

假设有一个Dummy的实例dummy。当您访问dummy的属性fibonacci时,它已被memoized装饰。属性fibonacci的值由memoized.__get__返回。__get__接受两个参数,一个是呼叫实例(这是dummy(,另一个是它的类型。memoized.__get__将实例填充到self.__call__中,以填充原始方法中的self参数fibonacci

很好地了解描述符,有一个示例:

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

最新更新