четверг, 15 марта 2012 г.

Декораторы. Поддержка множества экземпляров

Есть в Python такая штука, как декораторы. По сути, это обёртка для класса или функции, которая позволяет обслуживать создание экземпляров классов или вызов функций и оборачивать их дополнительной логикой. Есть в них несколько тонкостей, но одна из них у меня вызвала затруднение, разрешением которого я и хочу поделиться. Декоратор может быть определён как класс или как функция. И если определить декоратор класса как класс, то возникает одна проблема: создание каждого нового экземпляра затирает предыдущий. Для того, чтобы выяснить причину этого, я немного адаптировал пример из книжки Лутца "Изучаем Python". Вот что происходит, если декоратор определить как функцию:

>>> def decorator(cls):
 print ('in func; cls='+str(cls))
 class Wrapper:
  def __init__(self, *args):
   print ('in class init')
   self.wrapped=cls(*args)
   print ('self='+str(self)+' type(self)='+str(type(self))+'\nself.wrapped='+str(self.wrapped)+' type(self.wrapped)='+str(type(self.wrapped))+'\ncls='+str(cls)+' type(cls)='+str(type(cls)))
  def __getattr__(self,name):
   return getattr(self.wrapped,name)
 print (str(Wrapper)+'\t'+str(type(Wrapper)))
 return Wrapper
 
>>> @decorator
class C:
 def __init__(self, *args):
  self.attr=args
 
 
in func; cls=<class '__main__.C'>
<class '__main__.Wrapper'> <class 'type'>
>>> C, type(C)
(<class '__main__.Wrapper'>, <class 'type'>)
>>> x=C('asdf')
in class init
self=<__main__.Wrapper object at 0x010C9B90> type(self)=<class '__main__.Wrapper'>
self.wrapped=<__main__.C object at 0x010C9CD0> type(self.wrapped)=<class '__main__.C'>
cls=<class '__main__.C'> type(cls)=<class 'type'>
>>> y=C('asdf')
in class init
self=<__main__.Wrapper object at 0x010C7DB0> type(self)=<class '__main__.Wrapper'>
self.wrapped=<__main__.C object at 0x010C93D0> type(self.wrapped)=<class '__main__.C'>
cls=<class '__main__.C'> type(cls)=<class 'type'>
>>> x,type(x)
(<__main__.Wrapper object at 0x010C9B90>, <class '__main__.Wrapper'>)
>>> y,type(y)
(<__main__.Wrapper object at 0x010C7DB0>, <class '__main__.Wrapper'>) 
Как мы видим, на этапе использования декоратора создается только объект класса, но не экземпляр класса Wrapper, оригинальный же класс сохраняется в локальной области видимости функции в процессе компиляции (не вызова) объекта класса. А вот уже при создании экзепляров декорированного класса С, по сути экземпляров класса Wrapper, создаются разные экземпляры и объектов С и оборачивающего его Wrapper, как можно видеть по выводимым адресам. Что же происходит, если определить декоратор как класс?
>>> class Decorator:
 def __init__(self, C):
  print ('Decorator init')
  self.C=C
  print ('self='+str(self)+' type(self)='+str(type(self))+'\nself.C='+str(self.C)+' type(self.C)='+str(type(self.C))+'\nC='+str(C)+' type(C)='+str(type(C)))
 def __call__(self,*args):
  print ('Decorator call')
  self.wrapped=self.C(*args)
  print ('self='+str(self)+' type(self)='+str(type(self))+'\nself.wrapped='+str(self.wrapped)+' type(self.wrapped)='+str(type(self.wrapped))+'\ncls='+str(self.C)+' type(self.C)='+str(type(self.C)))
  return self
 def __getattr__(self, attrname):
  return getattr(self.wrapped,attrname)
 
 
>>> @Decorator
class C: pass
 
Decorator init
self=<__main__.Decorator object at 0x010A9FB0> type(self)=<class '__main__.Decorator'>
self.C=<class '__main__.C'> type(self.C)=<class 'type'>
C=<class '__main__.C'> type(C)=<class 'type'>
>>> x=C()
Decorator call
self=<__main__.Decorator object at 0x010A9FB0> type(self)=<class '__main__.Decorator'>
self.wrapped=<__main__.C object at 0x00C70830> type(self.wrapped)=<class '__main__.C'>
cls=<class '__main__.C'> type(self.C)=<class 'type'>
>>> y=C()
Decorator call
self=<__main__.Decorator object at 0x010A9FB0> type(self)=<class '__main__.Decorator'>
self.wrapped=<__main__.C object at 0x010A9FF0> type(self.wrapped)=<class '__main__.C'>
cls=<class '__main__.C'> type(self.C)=<class 'type'>
>>> x,type(x),y,type(y)
(<__main__.Decorator object at 0x010A9FB0>, <class '__main__.Decorator'>, <__main__.Decorator object at 0x010A9FB0>, <class '__main__.Decorator'>)
Тут мы видим, что на этапе декорирования создаётся [b]экземпляр[/b] класса Decorator! И при создании экземпляров декорируемого класса будет вызываться метод __call__, которому каждый раз будет передаваться данный экземпляр класса Decorator и будет затираться его атрибут wrapped. Но важно отметить, что если мы применим этот декоратор к другому классу это не затрет наш предыдущий декоратор. Да это и понятно, так как в данном случае будет просто создан ещё один экземпляр класса Decorator:
>>> @Decorator
class C: pass
 
Decorator init
self=<__main__.Decorator object at 0x010A9FB0> type(self)=<class '__main__.Decorator'>
self.C=<class '__main__.C'> type(self.C)=<class 'type'>
C=<class '__main__.C'> type(C)=<class 'type'>
>>> x=C()
Decorator call
self=<__main__.Decorator object at 0x010A9FB0> type(self)=<class '__main__.Decorator'>
self.wrapped=<__main__.C object at 0x00C70830> type(self.wrapped)=<class '__main__.C'>
cls=<class '__main__.C'> type(self.C)=<class 'type'>
>>> @Decorator
class D: pass
 
Decorator init
self=<__main__.Decorator object at 0x010B1030> type(self)=<class '__main__.Decorator'>
self.C=<class '__main__.D'> type(self.C)=<class 'type'>
cls=<class '__main__.D'> type(C)=<class 'type'>
>>> y=D()
Decorator call
self=<__main__.Decorator object at 0x010B1030> type(self)=<class '__main__.Decorator'>
self.wrapped=<__main__.D object at 0x00C70830> type(self.wrapped)=<class '__main__.D'>
cls=<class '__main__.D'> type(self.C)=<class 'type'>
>>> y,type(y)
(<__main__.Decorator object at 0x010B1030>, <class '__main__.Decorator'>)

7 комментариев:

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

    ОтветитьУдалить
    Ответы
    1. то есть? их реализацию или их применение?

      Удалить
    2. да, их применение... я ни как не могу найти полный пречень всех встренных декораторов, полазил по докам, и что-то как то мало нарыл :
      @classmethod
      @staticmethod
      @property

      Удалить
    3. Есть хорошая книжка Марка Лутца "Изучаем Python", там есть примеры их применения с объяснениями. Она у меня сейчас не под рукой, но на следующей неделе могу выложить оттуда примеры применения

      Удалить
    4. Я как-то вопрос не внимательно прочитал. Так вроде других встроенных декораторов-то и нет, как и необходимости в них. Они же достаточно просто сами реализуются.
      А примеры по тем трём, которые Вы нашли есть в стандартной документации.

      Удалить
    5. да я их оттуда и выдернул.., но у меня нет опыта их применения- это и усложняет понимание, да..было бы здорова на пальцах обьяснить стандарные декораторы, они ведь тоже часто используются...
      спс за автора, такую книжку знаю, но в нее не лазил

      Удалить
    6. тогда лучше там и посмотреть - лучше Лутца это не объяснить)

      Удалить