如何解决 `super()` 调用发现不正确的类型和对象?
How to troubleshoot `super()` calls finding incorrect type and obj?
我的库中有一个装饰器,它采用用户的 class 并创建它的新版本,使用新的元class,它应该完全替换原来的 [=54] =].一切正常;除了 super()
个电话:
class NewMeta(type):
pass
def deco(cls):
cls_dict = dict(cls.__dict__)
if "__dict__" in cls_dict:
del cls_dict["__dict__"]
if "__weakref__" in cls_dict:
del cls_dict["__weakref__"]
return NewMeta(cls.__name__, cls.__bases__, cls_dict)
@deco
class B:
def x(self):
print("Hi there")
@deco
class A(B):
def x(self):
super().x()
像这样使用此代码会产生错误:
>>> a = A()
>>> a.x()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in x
TypeError: super(type, obj): obj must be an instance or subtype of type
一些术语:
- 源代码 class
A
由 class A(B):
生成。
- 制作的 class
A*
,由 NewMeta(cls.__name__, cls.__bases__, cls_dict)
制作。
A
由 Python 在 A*
的方法中使用 super
时成为 type
。我该如何纠正?
有一些次优的解决方案,比如调用 super(type(self), self).x
,或者将 cls.__mro__
而不是 cls.__bases__
传递给 NewMeta
调用(这样 obj=self
总是继承自不正确的 type=A
)。第一个对于最终用户来说是不可持续的,第二个污染了继承链并且令人困惑,因为 class 似乎是从自身继承的。
Python好像是在反省源码,或者存储了一些信息来自动建立type
,在这种情况下,我会说它没有这样做;
如何确保 A
A*
的方法内部被建立为无参数 super
调用的 type
参数?
argument-freesuper
使用了__class__
单元格,这是一个常规的函数闭包。
Data Model: Creating the class object
__class__
is an implicit closure reference created by the compiler if any methods in a class body refer to either __class__
or super
.
>>> class E:
... def x(self):
... return __class__ # return the __class__ cell
...
>>> E().x()
__main__.E
>>> # The cell is stored as a __closure__
>>> E.x.__closure__[0].cell_contents is E().x() is E
True
与任何其他闭包一样,这是一个 词法 关系:它指的是 class
范围,方法在其中按字面定义。把class换成装饰器,方法还是参考原来的class。
最简单的修复方法是显式引用 class 的 name,它会被装饰器反弹到新创建的 class。
@deco
class A(B):
def x(self):
super(A, self).x()
或者,可以更改 __class__
单元格的内容以指向新的 class:
def deco(cls):
cls_dict = dict(cls.__dict__)
cls_dict.pop("__dict__", None)
cls_dict.pop("__weakref__", None)
new_cls = NewMeta(cls.__name__, cls.__bases__, cls_dict)
for method in new_cls.__dict__.values():
if getattr(method, "__closure__", None) and method.__closure__[0].cell_contents is cls:
method.__closure__[0].cell_contents = new_cls
return new_cls
我的库中有一个装饰器,它采用用户的 class 并创建它的新版本,使用新的元class,它应该完全替换原来的 [=54] =].一切正常;除了 super()
个电话:
class NewMeta(type):
pass
def deco(cls):
cls_dict = dict(cls.__dict__)
if "__dict__" in cls_dict:
del cls_dict["__dict__"]
if "__weakref__" in cls_dict:
del cls_dict["__weakref__"]
return NewMeta(cls.__name__, cls.__bases__, cls_dict)
@deco
class B:
def x(self):
print("Hi there")
@deco
class A(B):
def x(self):
super().x()
像这样使用此代码会产生错误:
>>> a = A()
>>> a.x()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in x
TypeError: super(type, obj): obj must be an instance or subtype of type
一些术语:
- 源代码 class
A
由class A(B):
生成。 - 制作的 class
A*
,由NewMeta(cls.__name__, cls.__bases__, cls_dict)
制作。
A
由 Python 在 A*
的方法中使用 super
时成为 type
。我该如何纠正?
有一些次优的解决方案,比如调用 super(type(self), self).x
,或者将 cls.__mro__
而不是 cls.__bases__
传递给 NewMeta
调用(这样 obj=self
总是继承自不正确的 type=A
)。第一个对于最终用户来说是不可持续的,第二个污染了继承链并且令人困惑,因为 class 似乎是从自身继承的。
Python好像是在反省源码,或者存储了一些信息来自动建立type
,在这种情况下,我会说它没有这样做;
如何确保 A
A*
的方法内部被建立为无参数 super
调用的 type
参数?
argument-freesuper
使用了__class__
单元格,这是一个常规的函数闭包。
Data Model: Creating the class object
__class__
is an implicit closure reference created by the compiler if any methods in a class body refer to either__class__
orsuper
.
>>> class E:
... def x(self):
... return __class__ # return the __class__ cell
...
>>> E().x()
__main__.E
>>> # The cell is stored as a __closure__
>>> E.x.__closure__[0].cell_contents is E().x() is E
True
与任何其他闭包一样,这是一个 词法 关系:它指的是 class
范围,方法在其中按字面定义。把class换成装饰器,方法还是参考原来的class。
最简单的修复方法是显式引用 class 的 name,它会被装饰器反弹到新创建的 class。
@deco
class A(B):
def x(self):
super(A, self).x()
或者,可以更改 __class__
单元格的内容以指向新的 class:
def deco(cls):
cls_dict = dict(cls.__dict__)
cls_dict.pop("__dict__", None)
cls_dict.pop("__weakref__", None)
new_cls = NewMeta(cls.__name__, cls.__bases__, cls_dict)
for method in new_cls.__dict__.values():
if getattr(method, "__closure__", None) and method.__closure__[0].cell_contents is cls:
method.__closure__[0].cell_contents = new_cls
return new_cls