__当元类调用多个super()时,classcell在Python3.6中生成错误

2024-06-25 23:44:13 发布

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

下面是一个在Python 2.7中工作但在Python 3.6中导致错误的可执行代码:

import six

class AMeta(type):

    def __new__(cls, name, bases, attrs):
        module = attrs.pop('__module__')
        new_attrs = {'__module__': module}
        classcell = attrs.pop('__classcell__', None)
        if classcell is not None:
            new_attrs['__classcell__'] = classcell
        new = super(AMeta, cls).__new__(
            cls, name, bases, new_attrs)
        new.duplicate = False
        legacy = super(AMeta, cls).__new__(
            cls, 'Legacy' + name, (new,), new_attrs)
        legacy.duplicate = True
        return new

@six.add_metaclass(AMeta)
class A():
    def pr(cls):
        print('a')

class B():
    def pr(cls):
        print('b')

class C(A,B):
    def pr(cls):
        super(C, cls).pr() # not shown with new_attrs
        B.pr(cls)
        print('c') # not shown with new_attrs
c = C()
c.pr()

# Expected result
# a
# b
# c

我得到以下错误:

Traceback (most recent call last):
  File "test.py", line 28, in <module>
    class C(A,B):
TypeError: __class__ set to <class '__main__.LegacyC'> defining 'C' as <class '__main__.C'>

C是从用元类AMeta生成的A继承的。它们是测试类,AMeta的目标是使用两个不同的文件夹执行所有测试:默认文件夹和遗留文件夹。你知道吗

我找到了一种方法来消除这个错误,从attrs中删除classcell,然后返回new=super(AMeta,cls)。new(cls,name,base,attrs)(不是new\u attrs),但它似乎不对,如果不对,我想知道原因。你知道吗

新属性的目标来自于这个SO questiondocumentation,它的状态基本上是相反的:在修改属性时,请确保保留classcell,因为它在Python3.6中不受欢迎,并且会导致Python3.8中的错误。 请注意,在这种情况下,它会删除pr定义,因为它们没有传递给新的属性,因此打印“b”而不是“abc”,但与此问题无关。你知道吗

有没有办法在元类AMeta的new内部调用多个super()。new,然后从继承a的类继承的类C调用它们?你知道吗

如果没有嵌套继承,则不会出现错误,如下所示:

import six

class AMeta(type):

    def __new__(cls, name, bases, attrs):
        new = super(AMeta, cls).__new__(
            cls, name, bases, attrs)
        new.duplicate = False
        legacy = super(AMeta, cls).__new__(
            cls, 'Duplicate' + name, (new,), attrs)
        legacy.duplicate = True
        return new

@six.add_metaclass(AMeta)
class A():
    def pr(cls):
        print('a')

a = A()
a.pr()

# Result

# a

那么也许是A的角色做些什么来修复它?你知道吗

提前谢谢


Tags: namenewdef错误prattrsclasscls
1条回答
网友
1楼 · 发布于 2024-06-25 23:44:13

你的问题是什么,以及如何解决 问题是,当您做您正在做的事情时,您将相同的cell对象传递给类的两个副本:原始副本和遗留副本。你知道吗

由于它同时存在于两个类中,当一个人试图使用它时,它会与它正在使用的另一个地方发生冲突-super()在调用时会选择错误的祖先类。你知道吗

cell对象很挑剔,它们是用本机代码创建的,不能在Python端创建或配置。我可以找到一种方法来创建类副本,方法是返回一个fresh cell对象,并将其作为__classcell__传递。你知道吗

(我还试着在classcell对象上运行copy.copy/copy.deepcopy然后再使用下面的cellfactory吼叫-它不起作用)

为了重现问题并找出解决方案,我制作了一个更简单的元类版本,Python3 only。你知道吗

from types import FunctionType
legacies = []

def classcellfactory():
    class M1(type):
        def __new__(mcls, name, bases, attrs, classcellcontainer=None):
            if isinstance(classcellcontainer, list):
                classcellcontainer.append(attrs.get("__classcell__", None))

    container = []

    class T1(metaclass=M1, classcellcontainer=container):
        def __init__(self):
            super().__init__()
    return container[0]


def cellfactory():
    x = None
    def helper():
        nonlocal x
    return helper.__closure__[0]

class M(type):
    def __new__(mcls, name, bases, attrs):
        cls1 = super().__new__(mcls, name + "1", bases, attrs)
        new_attrs = attrs.copy()
        if "__classcell__" in new_attrs:
            new_attrs["__classcell__"] = cellclass = cellfactory()

            for name, obj in new_attrs.items():
                if isinstance(obj, FunctionType) and obj.__closure__:
                    new_method = FunctionType(obj.__code__, obj.__globals__, obj.__name__, obj.__defaults__, (cellclass, ))
                    new_attrs[name] = new_method

        cls2 = super().__new__(mcls, name + "2", bases, new_attrs) 
        legacies.append(cls2)
        return cls1

class A(metaclass=M):
    def meth(self):
        print("at A")

class B(A): 
    pass

class C(B,A): 
    def meth(self):
        super().meth()

C()

因此,我不仅创建了一个嵌套函数,以便让Python运行时创建一个单独的cell对象,然后在克隆类中使用它,而且还必须使用指向新cell var的新的__closure__重新创建使用cell类的方法

如果不重新创建这些方法,它们将无法在clonned类中工作,因为克隆类中的super()方法将期望单元格指向克隆类本身,但它指向原始类。你知道吗

幸运的是,Python3中的方法是普通函数,这使得代码更简单。但是,该代码不会在Python2中运行-因此,只需将其包含在一个if块中,就不会在Python2上运行了。因为__cellclass__属性甚至不存在,所以根本没有问题。你知道吗

将上面的代码粘贴到Python shell中后,我可以运行这两个方法并且super()可以工作:

In [142]: C().meth()                                                                                                                              
at A

In [143]: legacies[-1]().meth()                                                                                                                   
at A

相关问题 更多 >