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

时间:2016-10-13 22:13:41

标签: python metaclass

在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高兴。但是,这不是我想要的。我希望动态创建公共子元类。

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未涵盖的元类的案例。

相关问题