【问题标题】:Decorator "object is not callable"装饰器“对象不可调用”
【发布时间】:2019-11-18 03:39:57
【问题描述】:

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

TypeError: 'CachedProperty' 对象不可调用。

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

当我单步执行代码时,装饰器在 CachedProperty 中调用 __init__ 时,当我导入 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

我从 botocore 中提取的 CachedProperty 类:

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

【问题讨论】:

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


    【解决方案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
    

    【讨论】:

    • 感谢 Olinox,这不是我想要的,因为如果我用不同的值再次调用它,我会得到这些值的总和,而不是缓存的结果。正如 jsbueno 在下面指出的那样,我完全错过了无论如何这应该缓存一个类的属性这一点。所以我现在专注于这个。但是,谢谢,您对可调用的 call 语法提出了一个很好的观点。
    【解决方案2】:

    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()

    【讨论】:

    • 谢谢 jsbueno,你说的很有道理。可调,我不认为我今天有一个非常清醒的一天,所以明天会回到这个。 :) 感谢您的帮助并指出我明显的错误...
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-01-03
    • 2020-08-19
    • 2013-01-10
    • 2021-11-22
    • 1970-01-01
    • 2018-05-07
    • 2020-09-22
    相关资源
    最近更新 更多