2016-01-22 6 views
4

У меня есть проблема с реализацией декоратора применяется в этом метаклассе декоратора я писал:метаклассов, которые украшают все методы класса с использованием два различные декораторы реализация

def decorateAll(decorator): 
    class MetaClassDecorator(type): 

     def __new__(meta, classname, supers, classdict): 
      for name, elem in classdict.items(): 
       if type(elem) is FunctionType: 
        classdict[name] = decorator(classdict[name]) 
      return type.__new__(meta, classname, supers, classdict) 
    return MetaClassDecorator 

Это класс, в котором я использовал метаклассом:

class Account(object, metaclass=decorateAll(Counter)): 

    def __init__(self, initial_amount): 
     self.amount = initial_amount 

    def withdraw(self, towithdraw): 
     self.amount -= towithdraw 

    def deposit(self, todeposit): 
     self.amount += todeposit 

    def balance(self): 
     return self.amount 

Все, кажется, прекрасно работает, когда я перехожу к украсьте метакласса декоратора реализован так:

def Counter(fun): 
    fun.count = 0 
    def wrapper(*args): 
     fun.count += 1 
     print("{0} Executed {1} times".format(fun.__name__, fun.count)) 
     return fun(*args) 
    return wrapper 

Но когда я использую декоратора реализован таким образом:

class Counter(): 

    def __init__(self, fun): 
     self.fun = fun 
     self.count = 0 

    def __call__(self, *args, **kwargs): 
     print("args:", self, *args, **kwargs) 
     self.count += 1 
     print("{0} Executed {1} times".format(self.fun.__name__, self.count)) 
     return self.fun(*args, **kwargs) 

Я получил эту ошибку:

line 32, in __call__ 
return self.fun(*args, **kwargs) 
TypeError: __init__() missing 1 required positional argument: 'initial_amount' 

Почему? Использование двух декораторов с другими функциями не показывает мне проблем. Я думаю, что проблема связана с тем, что методы, которые я пытаюсь украсить, - это методы класса. Я что-то упускаю?

ответ

2

Вам необходимо реализовать Counter в качестве дескриптора вызова. Когда в дескрипторе выполняется __get__, вы имитируете привязку дескриптора к экземпляру, который ему передан. Плюс сохранение счета на основе метода/объекта.

Этот код:

import collections 
import functools 
import types 


def decorateAll(decorator): 
    class MetaClassDecorator(type): 

     def __new__(meta, classname, supers, classdict): 
      for name, elem in classdict.items(): 
       if type(elem) is types.FunctionType: 
        classdict[name] = decorator(classdict[name]) 
      return type.__new__(meta, classname, supers, classdict) 
    return MetaClassDecorator 


class Counter(object): 
    def __init__(self, fun): 
     self.fun = fun 
     self.cache = {None: self} 
     self.count = collections.defaultdict(int) 

    def __get__(self, obj, cls=None): 
     if obj is None: 
      return self 

     try: 
      return self.cache[obj] 
     except KeyError: 
      pass 

     print('Binding {} and {}'.format(self.fun, obj)) 
     cex = self.cache[obj] = functools.partial(self.__call__, obj) 
     return cex 

    def __call__(self, obj, *args, **kwargs): 
     print("args:", obj, *args, **kwargs) 
     self.count[obj] += 1 
     print("{0} Exec {1} times".format(self.fun.__name__, self.count[obj])) 
     return self.fun(obj, *args, **kwargs) 


class Account(object, metaclass=decorateAll(Counter)): 

    def __init__(self, initial_amount): 
     self.amount = initial_amount 

    def withdraw(self, towithdraw): 
     self.amount -= towithdraw 

    def deposit(self, todeposit): 
     self.amount += todeposit 

    def balance(self): 
     return self.amount 


a = Account(33.5) 

print(a.balance()) 

производит следующий вывод:

Binding <function Account.__init__ at 0x000002250BCD8B70> and <__main__.Account object at 0x000002250BCE8BE0> 
args: <__main__.Account object at 0x000002250BCE8BE0> 33.5 
__init__ Exec 1 times 
Binding <function Account.balance at 0x000002250BCD8D90> and <__main__.Account object at 0x000002250BCE8BE0> 
args: <__main__.Account object at 0x000002250BCE8BE0> 
balance Exec 1 times 
33.5 

который вызывает __call__ метод дескриптора, сохраняет значение счетчика на по методу путем имитации привязки создания объекта type functools.partial.

+0

Когда, на ваш взгляд, удобнее использовать этот подход? – Nikaidoh

+0

В вашем случае это уместно, потому что вы украшаете обычные ** методы ** (те, которые принимают 'self' в качестве первого параметра, т. Е. Они привязаны к экземпляру во время создания экземпляра). Альтернативой является возвращение другого объекта (а не 'functools.partial'), который содержит ссылку на экземпляр (' obj') и связан с дескриптором – mementum