重写__class__会引发TypeError异常。

4

我正在尝试通过派生自该类并重写/添加某些方法来扩展用C编写的模块类的功能。

问题是,该模块在不同的地方创建了我尝试扩展的类的实例。因此,我需要创建一个强制转换方法,将模块提供的基类强制转换为具有额外功能的我的派生类。

以下是我尝试过的方法:

class Derived(Base):
    @classmethod
    def cast(cls, obj: Base):
        obj.__class__ = cls

这种方法适用于我自己创建的类层次结构,但是它与我使用的模块失败,并抛出以下异常:

TypeError:__class__分配仅支持堆类型或ModuleType子类

我很难找到关于这个异常的官方信息。任何信息都有帮助,即使是hacky的解决方案,只要它们干净并完全模拟我正在寻找的行为。

* 我试图扩展的类是包pygame中的Surface

1个回答

4
__class__属性一直受到限制,Python类比C语言定义的类型更加灵活。例如,数据模型文档中的__slots__部分指出:“只有在两个类都具有相同的__slots__时,才能使用__class__赋值。” 同一文档将实例.__class__属性称为“只读”。因此,__class__实际上并不是可写的,但它已经存在很长时间,并且是一个非常有用的属性。
现在,在您的情况下,该赋值是不允许的,因为目标实例属于不在堆上分配对象的类型(一个进程内存区域,用于容纳任意数量的对象,大多数 Python 对象都在此分配)。未在堆上分配的对象会以不同的方式进行管理(例如,不受引用计数的控制),如果更改它们的类型,则它们将突然需要以不同的方式进行管理。目前不支持这样做。
在 Python 3.5 发布候选版本中,曾经短暂地支持对模块设置__class__,但一旦发现可以更改内部不可变值的类型,这种支持就产生了不良后果:
class MyInt(int):
    # lets make ints mutable!

(1).__class__ = MyInt
1是一个内部值,因此现在Python程序中所有使用整数1的地方都已更改。这不是你想要的,特别是如果你像Google App Engine一样跨多个进程重用解释器内存!请参见问题#24912了解详情。

但这就是为什么模块实例在异常中被特别提到的原因,请参见{{link3:自定义模块属性访问}}。

您必须找到解决问题的不同方法。例如,也许您可以通过在某些东西中使用{{link4:__getattr__}}将实例包装起来以代理属性来解决您的具体问题。


我明白了,有什么变通的方法吗?我能把对象转移到堆上吗?重新初始化它,也许?我的意思是,我可以从非堆对象派生出来并进行更改。 - Nearoo
@Nearoo:你无法绕过它。你必须找到不同的方法,也许通过包装实例或编写一个C类型来绕过这些限制。 - Martijn Pieters
我明白了,那就是一个包装类。谢谢你! - Nearoo
您介意稍微整理一下您的回答吗?简短地解释一下并链接到官方来源,同时建议一个解决方法。包装类似乎很好用。我在这里找到了一个不错的示例 - Nearoo
@Nearoo:我不确定我想要缩短这个答案。我回答了你在这里提出的问题,未来阅读该问题的访问者需要我的答案中的信息才能理解发生了什么。 - Martijn Pieters
1
@Nearoo:一个包装类并不是通用的解决方案,它不能适用于所有情况,其中你想要强制将一个类型的实例变成另一种类型。 - Martijn Pieters

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接