2009-11-12 3 views
3

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

Мой декоратора выглядит следующим образом:

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: tuple index out of range, указывающую на мою функцию лямбда, как показано выше.

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

myStorageUnitInstance.someBigCalculation.invalidate(myStorageUnitInstance) 

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

ответ

2

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

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

-1

Хотя я согласен с 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' 

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

Смежные вопросы