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'> |
这个例子(当然将语法改为
问题1:我是否正确理解,在python 2中,第一个
问题2:(如何)是否可以使上面的示例在python 3中工作?我确实尝试使用
注意:当然,我可以通过静态地定义
在python 3中,在使用元类时,它必须准备好,并且它不能知道最后(非元类)类的基,以便在此时动态地创建元类。
但是,不要把事情复杂化(我承认我不能完全理解你对元类的需求),你可以简单地使用普通的类层次结构,并协作使用
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在某些元类构造函数中修补对
对于不需要显式地构建
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) |
(此代码与您的代码非常相似-但我使用了三行显式
您可以让它们将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中的一些选项。具体来说,
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。如果它们是为它设计的,那么就没有理由进行这种黑客式的运行时混合。