【问题标题】:Overwriting __class__ raises TypeError覆盖 __class__ 引发 TypeError
【发布时间】:2018-09-02 20:32:15
【问题描述】:

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

问题是他的模块创建了我试图在不同地方扩展的类的实例。因此,我需要创建一个转换方法,将模块提供的基类转换为我自己的具有附加功能的派生类。

这是我尝试过的:

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

此方法适用于我自己创建的类层次结构 - 但是它在我使用的模块中失败,引发以下异常:

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

我很难找到有关此异常的官方信息。任何信息都有帮助,我什至会接受 hacky 解决方案,只要它们是干净的并且完全模仿我正在寻找的行为。

* 我要扩展的类是 Surface 包内的 pygame

【问题讨论】:

    标签: python python-3.x casting typeerror python-internals


    【解决方案1】:

    __class__ 属性一直被限制在可接受的范围内,Python 类可以比 C 中定义的类型灵活得多。

    例如,__slots__ section in the datamodel documentation 声明:

    __class__ 分配仅在两个类具有相同的__slots__ 时才有效。

    并且同一个文档调用instance.__class__ attribute只读

    该实现为几个相关的对象类型添加了一些特殊的只读属性。

    所以__class__实际上并不是可写的,但它已经存在了很长时间并且是一个非常有用的属性。

    现在,在您的情况下,不允许分配,因为目标实例的类型不会在堆上分配对象(进程内存区域会增长以容纳任意数量的对象,它是大多数 Python 对象被分配)。未在堆上分配的对象的管理方式不同(例如,不受引用计数的影响),如果您更改了它们的类型,它们突然需要进行不同的管理。目前不支持。

    在 Python 3.5 候选发布版本中,briefly supported 允许在 模块 上设置 __class__,但是一旦发现可以更改内部不可变值的类型,这就会适得其反:

    class MyInt(int):
        # lets make ints mutable!
    
    (1).__class__ = MyInt
    

    1 是一个interned value,所以现在在 Python 程序中所有地方都使用整数 1 已更改。不是您想要的,尤其是当您像 Google App Engine 那样跨多个进程重用解释器内存时!请参阅issue #24912 了解详情。

    但这就是为什么在异常中特别提到模块实例的原因,请参阅Customising module attribute access

    您必须找到解决问题的不同途径。例如,也许您的特定问题可以通过包装使用__getattr__ 将属性代理到包装实例的实例来解决。

    【讨论】:

    • 我明白了。有解决方法吗?我可以将对象转移到堆上吗?重新初始化它,也许?我的意思是我可以很好地从非堆对象中派生并像那样更改它。
    • @Nearoo:你无法解决它。您必须找到一种不同的方法,可能是通过包装实例或在 C 中编写一个类型来绕过这些限制。
    • 我明白了。那么它就是一个包装类。谢谢!
    • 您介意清理一下您的答案吗?缩短解释并链接到官方资源,并提出解决方法。包装类似乎工作得很好。我找到了一个整洁的here
    • @Nearoo:包装器类不是通用解决方案,它不适用于您想要强制成为不同类型的一种类型的实例的所有情况。
    猜你喜欢
    • 2023-03-13
    • 2021-08-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-21
    • 1970-01-01
    相关资源
    最近更新 更多