装饰器“对象不可调用”

时间:2019-07-08 15:20:09

标签: python python-3.x python-decorators callable-object

我试图与Python中的装饰器打交道,并尝试从botocore库中实现CachedProperty装饰器的版本,但仍然遇到错误:

  

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

今天我已经使用Google搜索了一段时间,但是我发现的示例似乎并不直接等同于我的问题。它们主要与试图调用int和failure等对象的人有关。

当我单步执行代码时,装饰器在导入__init__时会以CachedProperty的方式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 类:

CachedProperty

看一下我最初从中刷过的程序,我希望它将sum函数传递给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

我实际上得到的是:

Option Explicit

2 个答案:

答案 0 :(得分:1)

您的sum_args被评估为CachedProperty,它没有实现任何__call__方法,因此无法调用。这就是为什么当您尝试使用sum_args(data)

调用python时引发此错误的原因

尝试将您的代码更改为:

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

答案 1 :(得分:1)

如其名称所述,

CachedProperty旨在用作类主体(不是独立函数)中方法的修饰器,其行为类似于普通的Python“属性”,但它们是“已缓存”。 :-)

在您的示例代码中,您试图将其应用于模块级函数,但这将不起作用-因为此装饰器将对象中的函数转换为依赖于仅对类成员起作用的属性访问机制的对象(即:“描述符协议”,适用于在__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()

中确实有一个装饰器。