Python中的 метaclasses:几个澄清问题

5 投票
2 回答
587 浏览
提问于 2025-04-16 20:44

在研究元类时,我开始深入了解Python中的元编程,但我有几个问题,在现有的文档中似乎没有明确的答案。

  1. 在元类中使用__new____init__时,它们的参数必须定义得一样吗?
  2. 在元类中定义类的__init__最有效的方法是什么?
  3. 在元类中,有没有办法引用类实例(通常是self)?

2 个回答

2
  1. 在元类中同时使用 __new____init__ 时,它们的参数必须定义得一样吗?

    我觉得 Alex Martelli 说得很简洁:

    class Name(Base1,Base2): <<body>>
        __metaclass__==suitable_metaclass
    

    这意味着

    Name = suitable_metaclass('Name', (Base1,Base2), <<dict-built-by-body>>)
    

    所以暂时不要把 suitable_metaclass 当成元类来看,而是把它当成一个普通的类。每当你看到

    suitable_metaclass('Name', (Base1,Base2), <<dict-built-by-body>>)
    

    这就告诉你,suitable_metaclass 的 __new__ 方法的参数应该类似于

    def __new__(metacls, name, bases, dct)
    

    __init__ 方法应该像这样

    def __init__(cls, name, bases, dct)
    

    所以它们的参数并不是完全一样,但只是在第一个参数上有所不同。

  2. 在元类中定义 __init__ 方法的最有效方式是什么?

    你说的有效是什么意思?其实如果你不想的话,根本不需要定义 __init__

  3. 在元类中有没有办法引用类实例(通常是 self)?

    没有,而且你也不应该需要这样做。任何依赖于类实例的东西应该在类定义中处理,而不是在元类中。

1

对于第一个问题:任何类的 __init____new__ 方法都必须接受相同的参数,因为它们会用相同的参数被调用。通常情况下,__new__ 方法会接受一些它会忽略的额外参数(比如 object.__new__ 方法可以接受任何参数,但会忽略它们),这样在继承的时候就不需要重写 __new__ 方法。不过,通常只有在你根本没有 __new__ 方法的时候才会这样做。

在这里并没有问题,因为正如所说的,元类总是用相同的一组参数被调用,所以在参数方面你不会遇到麻烦。但是如果你要修改传给父类的参数,那么你需要在两个地方都进行修改。

对于第二个问题:通常你不会在元类中定义类的 __init__ 方法。你可以写一个包装器,在元类的 __new____init__ 中替换类的 __init__ 方法,或者你可以在元类中重新定义 __call__ 方法。如果你使用继承,前者的表现会很奇怪。

import functools

class A(type):
    def __call__(cls, *args, **kwargs):
        r = super(A, cls).__call__(*args, **kwargs)
        print "%s was instantiated" % (cls.__name__, )
        print "the new instance is %r" % (r, )
        return r


class B(type):
    def __init__(cls, name, bases, dct):
        super(B, cls).__init__(name, bases, dct)
        if '__init__' not in dct:
            return
        old_init = dct['__init__']
        @functools.wraps(old_init)
        def __init__(self, *args, **kwargs):
            old_init(self, *args, **kwargs)
            print "%s (%s) was instantiated" % (type(self).__name__, cls.__name__)
            print "the new instance is %r" % (self, )
        cls.__init__ = __init__


class T1:
    __metaclass__ = A

class T2:
    __metaclass__ = B
    def __init__(self): 
        pass

class T3(T2):
    def __init__(self):
        super(T3, self).__init__()

这是调用它的结果:

>>> T1()
T1 was instantiated
the new instance is <__main__.T1 object at 0x7f502c104290>
<__main__.T1 object at 0x7f502c104290>
>>> T2()
T2 (T2) was instantiated
the new instance is <__main__.T2 object at 0x7f502c0f7ed0>
<__main__.T2 object at 0x7f502c0f7ed0>
>>> T3()
T3 (T2) was instantiated
the new instance is <__main__.T3 object at 0x7f502c104290>
T3 (T3) was instantiated
the new instance is <__main__.T3 object at 0x7f502c104290>
<__main__.T3 object at 0x7f502c104290>

对于第三个问题:是的,正如上面所示,来自 __call__

撰写回答