Подтвердить что ты не робот

Декоратор Python, сам путается

Я новичок в декораторах Python (ничего себе, отличная функция!), и мне нелегко заставить работать следующее, потому что аргумент self получает вид смешения.

#this is the decorator
class cacher(object):

    def __init__(self, f):
        self.f = f
        self.cache = {}

    def __call__(self, *args):
        fname = self.f.__name__
        if (fname not in self.cache):
            self.cache[fname] = self.f(self,*args)
        else:
            print "using cache"
        return self.cache[fname]

class Session(p.Session):

    def __init__(self, user, passw):
        self.pl = p.Session(user, passw)

    @cacher
    def get_something(self):
        print "get_something called with self = %s "% self
        return self.pl.get_something()

s = Session(u,p)
s.get_something()

Когда я запускаю это, я получаю:

get_something called with self = <__main__.cacher object at 0x020870F0> 
Traceback:
...
AttributeError: 'cacher' object has no attribute 'pl'

для строки, где я self.cache[fname] = self.f(self,*args)

Проблема. Очевидно, проблема заключается в том, что self является объектом cacher вместо экземпляра сеанса, который действительно не имеет атрибута pl. Однако я не могу найти, как это решить.

Решения, которые я рассмотрел, но не могу использовать - я думал о том, чтобы класс декоратора возвращал функцию вместо значения (как в разделе 2.1 этого article), так что self оценивается в правильном контексте, но это невозможно, поскольку мой декоратор реализован как класс и использует встроенный __call__. Затем я решил не использовать класс для моего декоратора, так что мне не нужен метод __call__, но я не могу этого сделать, потому что мне нужно поддерживать состояние между вызовами декоратора (т.е. Для отслеживания того, что находится в self.cache).

Вопрос. Итак, помимо использования глобальной переменной словаря cache (которую я не пробовал, но предполагаю, что она будет работать), есть ли другой способ заставить этот декоратор работать?

Изменить: этот вопрос SO похож на Украшая методы класса python, как передать экземпляр в декоратор?

4b9b3361

Ответ 1

Используйте протокол дескриптор следующим образом:

import functools

class cacher(object):

    def __init__(self, f):
        self.f = f
        self.cache = {}

    def __call__(self, *args):
        fname = self.f.__name__
        if (fname not in self.cache):
            self.cache[fname] = self.f(self,*args)
        else:
            print "using cache"
        return self.cache[fname]

    def __get__(self, instance, instancetype):
        """Implement the descriptor protocol to make decorating instance 
        method possible.

        """

        # Return a partial function with the first argument is the instance 
        #   of the class decorated.
        return functools.partial(self.__call__, instance)

Изменить:

Как это работает?

Использование протокола дескриптора в декораторе позволит нам получить доступ к методу, украшенному правильным экземпляром, как self, возможно, какой-то код может помочь лучше:

Теперь, когда мы это сделаем:

class Session(p.Session):
    ...

    @cacher
    def get_something(self):
        print "get_something called with self = %s "% self
        return self.pl.get_something()

эквивалентно:

class Session(p.Session):
    ...

    def get_something(self):
        print "get_something called with self = %s "% self
        return self.pl.get_something()

    get_something = cacher(get_something)

Итак, теперь get_something является экземпляром cacher. поэтому, когда мы будем называть метод get_something, он будет переведен на это (из-за протокола дескриптора):

session = Session()
session.get_something  
#  <==> 
session.get_something.__get__(get_something, session, <type ..>)
# N.B: get_something is an instance of cacher class.

и потому:

session.get_something.__get__(get_something, session, <type ..>)
# return
get_something.__call__(session, ...) # the partial function.

так

session.get_something(*args)
# <==>
get_something.__call__(session, *args)

Надеюсь, это объяснит, как это работает:)

Ответ 2

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

#this is the decorator
from functools import wraps
def cacher(f):
    # No point using a dict, since we only ever cache one value
    # If you meant to create cache entries for different arguments
    # check the memoise decorator linked in other answers
    print("cacher called")
    cache = []
    @wraps(f)
    def wrapped(*args, **kwds):
        print ("wrapped called")
        if not cache:
            print("calculating and caching result")
            cache.append(f(*args, **kwds))
        return cache[0]
    return wrapped

class C:
    @cacher
    def get_something(self):
        print "get_something called with self = %s "% self

C().get_something()
C().get_something()

Если вы не полностью знакомы с тем, как работают замыкания, добавление дополнительных заявлений печати (как я уже выше) может быть иллюстративным. Вы увидите, что cacher вызывается только как функция определена, но wrapped вызывается каждый раз, когда вызывается метод.

Это подчеркивает, что вам нужно быть осторожным с методами memoisation и методами экземпляра, хотя - если вы не будете осторожны, чтобы учитывать изменения в значении self, вы в конечном итоге будете делиться кешированными ответами между экземплярами, что может не то, что вы хотите.

Ответ 3

Сначала вы явно передаете объект cacher в качестве первого аргумента в следующей строке:

self.cache[fname] = self.f(self,*args)

Python автоматически добавляет self в список аргументов только для методов. Он преобразует функции (но не другие вызовы как ваш объект cacher!), Определенные в пространстве имен классов для методов. Чтобы получить такое поведение, я вижу два пути:

  • Измените декоратор, чтобы вернуть функцию, используя закрытие.
  • Внедрить протокол дескриптора, чтобы передать аргумент self самостоятельно, как это сделано в memoize decorator recipe.