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

2024-06-16 09:55:53 发布

您现在位置:Python中文网/ 问答频道 /正文

在Python2中,使用一个技巧可以创建一个具有多个基类的类,尽管这些基类的元类互为而不是子类。在

诀窍在于这些元类本身有一个元类(将其命名为“元元类”),并且该元元类为元类提供了一个调用方法,该方法可以在必要时动态创建基元类的公共子元类。最终,一个类的结果是其元类是新的子元类。代码如下:

>>> 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)等)在python3中不起作用。在

问题1:我是否正确地理解,在Python2中,第一个C3是使用第一个基的元类构造的,并且只有当{}不是{}和{}的普通子类时,才会出现错误,而在python3中,错误会提前出现?在

问题2:(如何)使上述示例在Python3中工作?我确实尝试过使用abc.ABCMeta的子类作为元元类,但是即使使用自定义的__subclasscheck__使issubclass(Meta1, Meta2)返回{},C3的创建仍然会导致错误。在

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


Tags: namemaintype子类classmetaclassc2bases
3条回答

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

至于你的一般情况,我相信梅森是对的,你可能把事情搞得太复杂了。我还没有看到pep447没有涉及到的组合元类的情况。在

下面是一个例子,它展示了python3.x中的一些选项,具体来说,C3有一个动态创建的元类,但是在很多方面都是显式的。C4有一个元类,它是在元类的函数中动态创建的。C5只是为了证明它也具有{}所具有的相同元类属性。(如果您使用函数作为元类而不是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()

需要注意的是,我们在这里使用fire(与您在原始示例中使用fire的方式相同)。不能保证元类在合作多重继承中能很好地发挥作用。如果它们不是为它而设计的,那么很有可能在某个点使用它时遇到bug。如果它们是为它而设计的,那么就没有理由进行这种骇人听闻的运行时混合:-)。在

在python3中,在使用元类时,它必须准备好,并且它不知道最终(非元)类的基,以便在那时动态地创建一个元类。在

但是,与其让事情复杂化(我承认我无法完全理解您对元类的需求),您可以简单地使用普通的类层次结构,协同使用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

以及:

^{pr2}$

如果你的元类一开始就不是为了协作而设计的,那么创建任何自动方法来组合它们是非常困难的——而且可能涉及到在一些元类构造函数中对type.__new__的调用进行monkey修补。在

至于不需要显式地构建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以保持元类顺序-这可能很重要)

您可以让它们将Auto作为派生类的元类传递,但在其他情况下,它的工作方式与您的示例相同:

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'

相关问题 更多 >