在Python 2中,通过一个技巧可以创建一个具有多个基础的类,尽管基础具有彼此不子类的元类。
诀窍是这些元类本身就是一个元类(将其命名为" metametaclass"),这个元类为元类提供了一个调用方法,可以动态创建基本元类的公共子元类,如有必要。最终,一个类结果的元类是新的子元类。这是代码:
>>> 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
,只有{{1}才会产生错误}不是type(C3)
和type(C1)
的常见子类,而在Python 3中错误是先前提出的?
问题2:(如何)是否可以使上述示例在Python 3中运行?我确实尝试使用type(C2)
的子类作为metametaclass,但即使使用自定义abc.ABCMeta
使__subclasscheck__
返回issubclass(Meta1, Meta2)
,C3的创建仍会导致错误
注意:当然,我可以通过静态定义True
并明确地将其用作Meta1Meta2
的元类来使Python 3高兴。但是,这不是我想要的。我希望动态创建公共子元类。
答案 0 :(得分:1)
在Python 3中,当使用元类时,它必须准备就绪,并且它不能知道最终(非元)类的基础,以便在该点动态创建元类。
但不是让事情变得复杂(我承认我不能满足你对元元类的需求) - 你可以简单地使用普通的类层次结构,并为你的元类协同使用super
。
您甚至可以使用简单的动态动态构建最终的元类
致电type
:
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
和
In[47] :Example.A
Out[47]: 'Metaclass A processed'
In[48]: Example.B
Out[48]: 'Metaclass A processed'
如果您的元类首先没有设计为协作,那么创建任何自动方法来组合它们将非常棘手 - 而且可能需要在某些方面修补调用type.__new__
的问题。元类构造函数。
至于不需要明确地构建C
,你可以使用普通函数作为元类参数,它将检查基数并构建动态派生元类:
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
来保留元类顺序 - 这可能很重要)
您可以将它们作为派生类的元类传递,但除此之外它在您的示例中有效:
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'
答案 1 :(得分:0)
这是一个示例,显示了python3.x中的一些选项。具体来说,C3
有一个动态创建的元类,但在很多方面都是明确的。 C4
有一个在其元类函数中动态创建的元类。 C5
只是为了证明它具有与C4
具有相同的元类属性。 (如果你使用函数作为元类而不是type
,我们没有通过继承失去任何东西,可以发生...)
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()
应该注意的是,我们在这里玩火(就像你在原始例子中玩火一样)。无法保证元类在协作多重继承中能够很好地发挥作用。如果它们不是为它而设计的,那么你可能会在某些点使用它来遇到错误。如果他们 为它设计,那么就没有理由做这个hacky运行时混合: - )。
答案 2 :(得分:0)
在95%的情况下,应该可以使用Python 3.6中引入的机制,因为PEP 447使用特殊的新钩子来完成大多数元类可以做的事情。在这种情况下,您不需要组合元类,因为您的钩子可以调用super,并且由于继承而将它们的行为组合在一起。
至于你的一般情况,我相信mgilson是正确的,你可能会让事情变得太复杂。我还没有看到结合PEP 447未涵盖的元类的案例。