1

我正在尝试使用 Python 中的装饰器并尝试CachedProperty从 botocore 库中实现装饰器的一个版本,但一直遇到错误:

TypeError:“CachedProperty”对象不可调用。

我今天已经在谷歌上搜索了一段时间,但我发现的例子似乎并不直接等同于我的问题。它们主要与试图调用 int 和失败之类的对象的人有关。

当我单步执行代码时,装饰器__init__CachedProperty导入时调用 ok sum_args(),但是当我从单元测试中调用函数本身时会引发错误。

我的单元测试:

import unittest

from decorators.caching_example import sum_args

class TestCachedProperty(unittest.TestCase):

    def test_sum_integers(self):
        data = [1, 2, 3]
        result = sum_args(data)
        self.assertEqual(result, 6)

我要装饰的功能:

from decorators.caching_property import CachedProperty

@CachedProperty
def sum_args(arg):
    total = 0
    for val in arg:
        total += val
    return total

CachedProperty我从 botocore 学到的课程:

class CachedProperty(object):
    """A read only property that caches the initially computed value.

    This descriptor will only call the provided ``fget`` function once.
    Subsequent access to this property will return the cached value.

    """

    def __init__(self, fget):
        self._fget = fget

    def __get__(self, obj, cls):
        if obj is None:
            return self
        else:
            computed_value = self._fget(obj)
            obj.__dict__[self._fget.__name__] = computed_value
            return computed_value

查看我最初从中刷出的程序,我希望它能够将 sum 函数传递给CachedProperty类——在运行时创建它的实例——并将结果存储在其内部实例变量中的实例self._fget

我实际上得到的是:

Error
Traceback (most recent call last):
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/case.py", line 59, in testPartExecutor
    yield
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/case.py", line 615, in run
    testMethod()
  File "/Users/bradley.atkins/PycharmProjects/brad/examples/tests/decorators/test_property_cache.py", line 11, in test_sum_integers
    result = sum_args(data)
TypeError: 'CachedProperty' object is not callable
4

2 回答 2

1

sum_args被评估为 a CachedProperty,它不实现任何__call__方法,使其不可调用。这就是为什么当你尝试调用它时python会抛出这个错误sum_args(data)

尝试将您的代码更改为:

class CachedProperty(object):

    def __init__(self, fget):
        self._fget = fget

    def __call__(self, obj):
        if obj is None:
            return obj
        else:
            computed_value = self._fget(obj)
            self.__dict__[self._fget.__name__] = computed_value
            return computed_value

@CachedProperty
def sum_args(arg):
    total = 0
    for val in arg:
        total += val
    return total

data = [1, 2, 3]
result = sum_args(data)
print(result) # >> 6
于 2019-07-08T15:36:49.710 回答
0

CachedProperty,如其名称所述,旨在用作类主体(不是独立函数)中方法的装饰器,其行为类似于普通的 Python“属性”,但被“缓存”。:-)

在您的示例代码中,您试图将其应用于模块级函数,但这不起作用 - 因为此装饰器将函数转换为一个对象,该对象依赖于仅适用于类成员的属性访问机制(即:“描述符协议”,适用于实现一个 on__get____set__方法的对象__del__)。

实际上,Python 中装饰器的想法非常简单——它没有特殊情况。您的代码中明显的“特殊情况”是由于返回对象的性质造成的。

所以,简而言之-装饰器只是一个可调用的,它接受一个唯一的参数,它是另一个可调用的 - 通常是一个函数或一个类,并返回另一个对象(不一定是可调用的),它将替换第一个对象。

所以给定一个简单的装饰器,例如:

def logcalls(func):
    def wrapper(*args, **kw):
        print(f"{func} called with {args} and {kw}")
        return func(*args, **kw)
    return wrapper

它可以用作:

@logcalls
def add(a, b):
   return a + b

这相当于:

def add(a, b):
    return a + b
add = logcalls(a + b)

就这么简单!

当您想为装饰器传递额外的参数时,可能会出现复杂性,那么您需要有一个“阶段”来接受这些配置参数,并返回一个可调用对象,该可调用对象将被装饰对象作为其单个参数。在一些导致装饰器由 3 级嵌套函数组成的代码库中,这可能会让人心烦意乱。

如果CachedProperty上面会实现一个__call__方法,此外__get__,它也适用于模块类(假设它有一个合适的地方来记录类值 - 描述符免费获得它们附加到的实例)。另外,值得注意的是,为了缓存对普通函数的调用,Python 的标准库在functools模块中确实有一个装饰器——functools.lru_cache()

于 2019-07-08T15:37:15.170 回答