以下是一个可执行代码,它在 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 的目标是使用 2 个不同的文件夹执行所有测试:默认文件夹和旧文件夹。
我找到了一种消除此错误的方法,即从attrs 中删除classcell ,然后返回new = super(AMeta, cls)。new ( cls, name, bases, attrs)(而不是new_attrs),但这似乎不正确,如果是这样,我想知道原因。
new_attrs 的目标源自这个SO 问题或文档,其中基本上陈述了相反的内容:修改 attrs 时,请确保保留classcell,因为它在 Python 3.6 中已弃用,并将导致 Python 3.8 中的错误。请注意,在这种情况下,它会删除 pr 定义,因为它们未传递给new_attrs,因此会打印“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 的职责就是做些事情来解决这个问题?
我可以弄清楚你的问题是什么,以及如何解决它问题是,当你做你正在做的事情时,你将同一个 cell对象传递给你的类的两个副本:原始副本和遗留副本。
cell
由于它同时存在于两个类中,当尝试使用它时,它会与正在使用它的其他地方发生冲突 -super()调用时会选择错误的祖先类。
super()
cell对象很挑剔,它们是用本机代码创建的,无法在 Python 端创建或配置。我可以想出一种创建类副本的方法,即使用一个方法返回一个新的单元格对象,并将其作为传递__classcell__。
__classcell__
(在诉诸我的吼叫之前,我也尝试过在对象上运行copy.copy/ - 但是它不起作用)copy.deepcopy``classcell``cellfactory
copy.copy
copy.deepcopy``classcell``cellfactory
为了重现该问题并找出解决方案,我制作了一个更简单的元类版本,仅限 Python3。
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 运行时创建一个单独的单元对象,然后在克隆的类中使用该单元对象 - 而且,还必须使用指向__closure__新单元变量的新方法重新创建利用单元类的方法。
__closure__
如果不重新创建这些方法,它们将无法在克隆的类中工作 - 因为super()克隆的类的方法期望单元格指向克隆的类本身,但它却指向原始类。
幸运的是,Python 3 中的方法是普通函数 - 这使得代码更简单。但是,该代码无法在 Python 2 中运行 - 因此,只需将其括在一个if块中即可不在 Python2 上运行。由于该__cellclass__属性在那里甚至不存在,所以根本没有问题。
if
__cellclass__
将上述代码粘贴到 Python shell 中后,我可以运行这两种方法并super()运行:
In [142]: C().meth() at A In [143]: legacies[-1]().meth() at A