Декораторы Python, методы класса и оценка - django memoize - PullRequest
3 голосов
/ 12 ноября 2009

У меня есть рабочий декоратор memoize, который использует кеш-память Django для запоминания результата функции в течение определенного промежутка времени. Я специально применяю это к методу класса.

Мой декоратор выглядит так:

def memoize(prefix='mysite', timeout=300, keygenfunc=None):
    # MUST SPECIFY A KEYGENFUNC(args, kwargs) WHICH MUST RETURN A STRING
    def funcwrap(meth):

      def keymaker(*args, **kwargs):
        key = prefix + '___' + meth.func_name + '___' + keygenfunc(args, kwargs)
        return key

      def invalidate(*args, **kwargs):
        key = keymaker(*args, **kwargs)
        cache.set(key, None, 1)

      def newfunc(*args, **kwargs):
        # construct key
        key = keymaker(*args, **kwargs)

        # is in cache?
        rv = cache.get(key)

        if rv is None:
          # cache miss
          rv = meth(*args, **kwargs)
          cache.set(key, rv, timeout)

        return rv

      newfunc.invalidate = invalidate
      return newfunc
    return funcwrap

Я использую это для метода класса, так что-то вроде:

class StorageUnit(models.Model):
  @memoize(timeout=60*180, keygenfunc=lambda x,y: str(x[0].id))
  def someBigCalculation(self):
    ...
    return result

Фактический процесс запоминания работает отлично! То есть звонок на

myStorageUnitInstance.someBigCalculation()

правильно использует кеш. ОК, круто!

Моя проблема возникает, когда я пытаюсь вручную сделать недействительной запись для определенного экземпляра, где я хочу иметь возможность запустить

myStorageUnitInstance.someBigCalculation.invalidate()

Однако, это не работает, потому что «я» не передается и, следовательно, ключ не получается. Я получаю ошибку «IndexError: индекс кортежа вне диапазона», указывающий на мою лямбда-функцию, как показано ранее.

Конечно, я могу успешно позвонить:

myStorageUnitInstance.someBigCalculation.invalidate(myStorageUnitInstance)

и это прекрасно работает. Но это "кажется" излишним, когда я уже ссылаюсь на конкретный экземпляр. Как я могу заставить Python рассматривать это как метод, привязанный к экземпляру, и, следовательно, правильно заполнять переменную "self"?

Ответы [ 2 ]

2 голосов
/ 12 ноября 2009

Дескрипторы всегда должны быть установлены для класса, а не для экземпляра (подробности см. В руководстве ). Конечно, в этом случае вы даже не устанавливаете его на экземпляр, а скорее на другую функцию (и выбираете его как атрибут связанного метода). Я думаю, что единственный способ использовать синтаксис, который вы хотите, - это сделать funcwrap экземпляром пользовательского класса (конечно, этот класс должен быть классом дескриптора, т. Е. Определить соответствующий метод __get__, как это делают функции по сути) , Тогда invalidate может быть методом этого класса (или, может быть, лучше, другого пользовательского класса, экземпляром которого является «вещество, похожее на связанный метод», созданное методом __get__ ранее описанного класса дескриптора), и в конечном итоге достичь im_self (именно так оно и называется в связанном методе), которого вы жаждете.

Довольно здоровенная (концептуальная и кодирующая ;-) цена, которую нужно заплатить за незначительное удобство, которое вы ищете - достаточно дорого, чтобы мне не хотелось тратить час или два на его разработку и тестирование. Но я надеюсь, что дал вам достаточно четких указаний для того, чтобы вы продолжали, если вы все еще заинтересованы в этом, и, действительно, я буду рад прояснить и помочь, если что-то непонятное или что-то ставит вас в тупик на этом пути.

0 голосов
/ 12 ноября 2009

Хотя я согласен с AlexM, у меня было немного свободного времени и я подумал, что это будет интересно:

# from django.whereever import cache
class memoize(object):
    def __init__(self,prefix='mysite', timeout=300, keygenfunc=None):
        class memo_descriptor(object):
            def __init__(self,func):
                self.func = func
            def __get__(self,obj,klass=None):
                key = prefix + '___' + self.func.func_name + '___' + keygenfunc(obj)
                class memo(object):
                    def __call__(s,*args,**kwargs):
                        rv = cache.get(key)
                        if rv is None:
                            rv = self.func(obj,*args, **kwargs)
                            cache.set(key, rv, timeout)
                        return rv
                    def invalidate(self):
                        cache.set(key, None, 1)
                return memo()
        self.descriptor = memo_descriptor
    def __call__(self,func):
        return self.descriptor(func)

Примечание. Я изменил сигнатуру keygenfunc с (*args,**kwargs) на (instance), поскольку именно так вы и использовали ее в своем примере (и невозможно, чтобы метод someBigCalculation.invalidate очистил кэш таким образом вы хотели, если вы генерируете ключ из аргументов вызова метода, а не экземпляра объекта).

class StorageUnit(models.Model):
    @memoize(timeout=60*180, keygenfunc=lambda x: str(x.id))
    def someBigCalculation(self):
        return 'big calculation'

В этом коде происходит много всего, поэтому стоит подумать, действительно ли это облегчает вашу жизнь.

...