使用两种不同的装饰器实现来装饰所有类方法的元类

Posted

技术标签:

【中文标题】使用两种不同的装饰器实现来装饰所有类方法的元类【英文标题】: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'

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

【问题讨论】:

【参考方案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)并链接到描述符

以上是关于使用两种不同的装饰器实现来装饰所有类方法的元类的主要内容,如果未能解决你的问题,请参考以下文章

Python 元类与类装饰器

我应该使用元类、类装饰器还是重写 __new__ 方法?

在类中的所有实例方法自动调用之后调用方法:元类还是装饰器?

派生类的类/元类方法装饰器

Python之路:描述符,类装饰器,元类

Python入门之python装饰器的4种类型:函数装饰函数函数装饰类类装饰函数类装饰类