Декоратор "Объект не вызывается" - PullRequest
2 голосов
/ 08 июля 2019

Я пытаюсь разобраться с декораторами в Python и пытаюсь реализовать версию декоратора CachedProperty из библиотеки botocore, но продолжаю выдавать ошибку:

TypeError: объект 'CachedProperty' не может быть вызван.

Сегодня я уже некоторое время гуглял, но примеры, которые я нашел, похоже, не являются прямым эквивалентом моей проблемы. Они в основном относятся к людям, пытающимся называть объекты как int и терпящие неудачу.

Когда я выполняю код, декоратор вызывает __init__ in 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, который я поднял с ботокора:

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

Ответы [ 2 ]

1 голос
/ 08 июля 2019

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

1 голос
/ 08 июля 2019

Ваш sum_args оценивается как 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
...