【问题标题】:Metaclass which decorate all the class methods using two different decorator implementation使用两种不同的装饰器实现来装饰所有类方法的元类
【发布时间】:2016-04-29 07:48:50
【问题描述】:

我在我写的这个元类装饰器上应用的装饰器的实现有问题:

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'

为什么?将这两个装饰器实现与其他功能一起使用不会给我带来问题。我认为这个问题与我试图装饰的方法是类方法有关。我错过了什么吗?

【问题讨论】:

    标签: python decorator metaclass class-method


    【解决方案1】:

    您需要将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__ 方法,通过模拟创建functools.partial 类型对象的绑定将计数存储在每个方法上。

    【讨论】:

    • 您认为什么时候使用这种方式更方便?
    • 在您的情况下它是合适的,因为您正在装饰常规 方法(那些以 self 作为第一个参数的方法,即:它们在实例化时绑定到实例)。另一种方法是返回另一个对象(而不是functools.partial),该对象包含对实例的引用(obj)并链接到描述符
    猜你喜欢
    • 2014-01-14
    • 2020-01-11
    • 2014-04-08
    • 2020-11-30
    • 1970-01-01
    • 2019-02-03
    相关资源
    最近更新 更多