通过覆盖__new__装饰类方法不起作用?

时间:2019-02-04 17:02:43

标签: python metaclass python-internals

我想装饰我班上所有的方法。我在这里写了一个小小的装饰器示例用于说明。

装饰器:

def debug(func):
    msg = func.__name__

    @wraps(func)
    def wrapper(*args, **kwargs):
        print(msg)
        return func(*args, **kwargs)
    return wrapper 

def debugmethods(cls):
    for key, val in vars(cls).items():
        if callable(val):
            setattr(cls, key, debug(val))
    return cls

现在,我想装饰我班上的所有方法。一种简单的方法是在类的顶部使用@debugmethods批注,但我正在尝试了解另外两种不同的方法。

a)覆盖__new__

class Spam:
    def __new__(cls, *args, **kwargs):
        clsobj = super().__new__(cls)
        clsobj = debugmethods(clsobj)
        return clsobj

    def __init__(self):
        pass

    def foo(self):
        pass

    def bar(self):
        pass

spam = Spam()
spam.foo()

b)编写元类

class debugmeta(type):
    def __new__(cls, clsname, bases, clsdict):
        clsobj = super().__new__(cls, clsname, bases, clsdict)
        clsobj = debugmethods(clsobj)
        return clsobj

class Spam(metaclass = debugmeta):     
    def foo(self):
        pass

    def bar(self):
        pass

spam = Spam()
spam.foo()

我不确定

  1. 为什么“ a)覆盖__new__”不起作用?
  2. 为什么方法__new__的签名在元类中不同?

有人可以帮助我了解我在这里想念的是什么。

1 个答案:

答案 0 :(得分:2)

您似乎对__new__和元类感到困惑。调用__new__来创建一个新对象(一个类的实例,一个元类的类),它不是一个“创建的类”钩子。

正常模式是:

  • Foo(...)已翻译为type(Foo).__call__(Foo, ...),请参阅{{3}}。类的type()是它的元类。
  • type.__call__是自定义Python类时使用的标准Foo实现将调用__new__创建一个新实例,然后在该实例上调用__init__方法如果结果确实是Foo类的实例,则为instance:

    def __call__(cls, *args, **kwargs):  # Foo(...) -> cls=Foo
        instance = cls.__new__(cls, *args, **kwargs)  # so Foo.__new__(Foo, ...)
        if isinstance(instance, cls):
            instance.__init__(*args, **kwargs)        # Foo.__init__(instance, ...)
        return instance
    

    因此,仅在创建Foo.__new__实例时,才在创建Foo类本身时不调用Foo

您通常不需要在类中使用__new__,因为__init__足以初始化实例的属性。但是对于不可变类型,例如inttuple,您只能使用__new__来准备新的实例状态,因为您不能更改的属性一旦创建了一个不变的对象。如果您想更改__new__产生哪种类型的实例(例如创建单例或代替生成专门的子类),ClassObj()也很有帮助。

相同的__call__-> __new__以及也许__init__过程适用于元类。通过调用元类创建一个类对象来实现class Foo: ...语句,该类对象通常作为字典传入3个参数:类名,类基数和类主体。对于class Spam(metaclass = debugmeta): ...,这意味着debugmeta('Spam', (), {...})被调用,这意味着debugmeta.__new__(debugmeta, 'Spam', (), {...})被调用。

您的第一次尝试 a 设置Spam.__new__不起作用,因为您在那里没有创建类对象。相反,super().__new__(cls)创建一个没有属性的空Spam() 实例,因此vars()返回一个空字典,而debugmethods()最终什么也不做。

如果您想参与类的创建,那么就需要一个元类。