如何解决 `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

一些术语:

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