在Python 3中,是否可以为具有多个基础的类动态创建元类?

Is it possible to dynamically create a metaclass for a class with several bases, in Python 3?

在python 2中,通过一个技巧,可以创建一个具有多个基的类,尽管基具有彼此不属于子类的元类。

诀窍是这些元类本身具有一个元类(将其命名为"元类"),并且这个元类为这些元类提供了一个调用方法,如果需要,可以动态地创建基本元类的公共子元类。最后,类的元类是新的子元类。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
>>> class MetaMeta(type):
...     def __call__(mcls, name, bases, methods):
...         metabases = set(type(X) for X in bases)
...         metabases.add(mcls)
...         if len(metabases) > 1:
...             mcls = type(''.join([X.__name__ for X in metabases]), tuple(metabases), {})
...         return mcls.__new__(mcls, name, bases, methods)
...
>>> class Meta1(type):
...     __metaclass__ = MetaMeta
...
>>> class Meta2(type):
...     __metaclass__ = MetaMeta
...
>>> class C1:
...     __metaclass__ = Meta1
...
>>> class C2:
...     __metaclass__ = Meta2
...
>>> type(C1)
<class '__main__.Meta1'>
>>> type(C2)
<class '__main__.Meta2'>
>>> class C3(C1,C2): pass
...
>>> type(C3)
<class '__main__.Meta1Meta2'>

这个例子(当然将语法改为class C1(metaclass=Meta1)等)在python 3中不起作用。

问题1:我是否正确理解,在python 2中,第一个C3是使用第一个基的元类构造的,并且只有当type(C3)不是type(C1)type(C2)的公共子类时才会导致错误,而在python 3中,错误是较早出现的?

问题2:(如何)是否可以使上面的示例在python 3中工作?我确实尝试使用abc.ABCMeta的一个子类作为元类,但是即使使用自定义__subclasscheck__使issubclass(Meta1, Meta2)返回True,c3的创建仍然会导致错误。

注意:当然,我可以通过静态地定义Meta1Meta2并将其显式地用作C3的元类来让python 3高兴。但是,这不是我想要的。我希望动态创建公共子元类。


在python 3中,在使用元类时,它必须准备好,并且它不能知道最后(非元类)类的基,以便在此时动态地创建元类。

但是,不要把事情复杂化(我承认我不能完全理解你对元类的需求),你可以简单地使用普通的类层次结构,并协作使用super来处理元类。甚至可以使用简单的呼叫type

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class A(type):
    def __new__(metacls, name, bases,attrs):
        attrs['A'] ="Metaclass A processed"
        return super().__new__(metacls, name, bases,attrs)


class B(type):
    def __new__(metacls, name, bases,attrs):
        attrs['B'] ="Metaclass A processed"
        return super().__new__(metacls, name, bases,attrs)


C = type("C", (A, B), {})

class Example(metaclass=C): pass

还有:

1
2
3
4
5
In[47] :Example.A
Out[47]: 'Metaclass A processed'

In[48]: Example.B
Out[48]: 'Metaclass A processed'

如果您的元类最初不是设计为协作的,那么要创建任何自动方法来组合它们将是非常困难的,而且它可能涉及到monkey在某些元类构造函数中修补对type.__new__的调用。

对于不需要显式地构建C,可以使用一个普通函数作为元类参数,它将检查基并构建一个动态派生的元类:

1
2
3
4
5
6
7
8
def Auto(name, bases, attrs):
    basemetaclasses = []
    for base in bases:
        metacls = type(base)
        if isinstance(metacls, type) and metacls is not type and not metacls in basemetaclasses:
            basemetaclasses.append(metacls)
    dynamic = type(''.join(b.__name__ for b in basemetaclasses), tuple(basemetaclasses), {})
    return dynamic(name, bases, attrs)

(此代码与您的代码非常相似-但我使用了三行显式for而不是set,以保留元类顺序-这可能很重要)

您可以让它们将auto作为派生类的元类传递,但除此之外,它的工作方式与您的示例相同:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
In [61]: class AA(metaclass=A):pass

In [62]: class BB(metaclass=B):pass

In [63]: class CC(AA,BB): pass
---------------------------------------------------------------------------
...
TypeError:   metaclass conflict
...

In [66]: class CC(AA,BB, metaclass=Auto): pass

In [67]: type(CC)
Out[67]: __main__.AB

In [68]: CC.A
Out[68]: 'Metaclass A processed'


在95%的情况下,应该可以使用python 3.6中引入的机制,因为pep 447可以使用特殊的新钩子完成元类可以做的大部分工作。在这种情况下,您将不需要组合元类,因为您的钩子可以调用super,并且它们的行为是由于继承而组合的。

至于你的一般情况,我相信米吉尔森是对的,你可能把事情搞得太复杂了。我还没有看到一个合并不包含在PEP447中的元类的案例。


下面是一个例子,展示了python3.x中的一些选项。具体来说,C3有一个动态创建的元类,但在很多方面是明确的。C4有一个元类,它在元类函数中动态创建。C5只是为了证明它也具有与C4相同的元类属性。(我们没有通过继承丢失任何东西,如果您将函数用作元类而不是type…)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class Meta1(type):
    def foo(cls):
        print(cls)


class Meta2(type):
    def bar(cls):
        print(cls)


class C1(object, metaclass=Meta1):
   """C1"""


class C2(object, metaclass=Meta2):
   """C2"""


class C3(C1, C2, metaclass=type('Meta3', (Meta1, Meta2), {})):
   """C3"""

def meta_mixer(name, bases, dct):
    cls = type('MixedMeta', tuple(type(b) for b in bases), dct)
    return cls(name, bases, dct)


class C4(C1, C2, metaclass=meta_mixer):
   """C4"""


C1.foo()
C2.bar()

C3.foo()
C3.bar()

C4.foo()
C4.bar()

class C5(C4):
   """C5"""

C5.foo()
C5.bar()

应该注意的是,我们在这里玩火(与您在原始示例中玩火的方式相同)。不能保证元类在合作的多重继承中发挥良好的作用。如果它们不是为它设计的,那么很可能在某个时候使用它会遇到bug。如果它们是为它设计的,那么就没有理由进行这种黑客式的运行时混合。