nbe*_*ben 7 python types cpython python-c-api python-3.x
假设我使用 C 扩展 API 实现了两种 Python 类型,并且这些类型是相同的(相同的数据布局/C struct),除了它们的名称和一些方法之外。假设所有方法都遵循数据布局,您可以在 C 函数中安全地将对象的类型从其中一种类型更改为另一种类型吗?
值得注意的是,从 Python 3.9 开始,似乎有一个 function Py_SET_TYPE,但文档并不清楚是否/何时可以安全地执行此操作。我有兴趣了解如何安全地使用此函数以及在 3.9 版本之前是否可以安全地更改类型。
我正在编写一个 Python C 扩展来实现持久哈希数组映射 Trie (PHAMT);如果它有用,源代码在这里(截至撰写本文时,它位于此提交)。我想添加的一个功能是能够从 PHAMT 创建瞬态哈希数组映射 Trie (THAMT)。THAMT 可以及时从 PHAMT 中产生O(1),并且可以有效地就地突变。重要的是,THAMT 与 PHAMT\xe2\x80\x94 具有完全相同的底层C 数据结构。PHAMT和 THAMT 之间的唯一真正区别是它们的 Python 类型封装的一些方法。这种通用结构允许人们在完成一组编辑后非常有效地将 THAMT 转换回 PHAMT。(当对 PHAMT 执行大量更新时,此模式通常会减少内存分配的数量)。
实现从 THAMT 到 PHAMT 的转换的一种非常方便的方法是简单地将 THAMT 对象的类型指针从 THAMT 类型更改为 PHAMT 类型。我相信我可以编写安全地导航此更改的代码,但我可以想象这样做可能会破坏 Python 垃圾收集器。
\n(需要明确的是:动机只是问题如何产生的背景。我不是在寻求实现动机中描述的结构的帮助,我正在寻找上面问题的答案。)
\n只要内存布局兼容,官方就可以在 Python 中更改对象的类型...但这主要限于 C 中未实现的类型。有一些限制,可以这样做
# Python attribute assignment, not C struct member assignment
obj.__class__ = some_new_class
Run Code Online (Sandbox Code Playgroud)
更改对象的类,限制之一是旧类和新类都必须是“堆类型”,Python 中实现的所有类都是“堆类型”,而 C 中实现的大多数类都不是。(尽管不是堆types.ModuleType类型,但该类型的子类也是特别允许的。有关确切的限制,请参阅源代码。)types.ModuleType
如果你想从 C 创建一个堆类型,你可以,但是这个接口与从 C 定义 Python 类型的正常方式有很大不同。另外,为了让__class__赋值工作,你必须不设置标志Py_TPFLAGS_IMMUTABLETYPE,这意味着人们将能够以您可能不喜欢的方式对您的课程进行猴子修补(或者您可能认为这是一个好处)。
如果您想走这条路,我建议查看CPython 3.10_functools模块源代码的示例。(他们设置了Py_TPFLAGS_IMMUTABLETYPE标志,你必须确保不要这样做。)
曾一度尝试允许__class__对非堆类型进行分配,只要内存布局有效。它被放弃是因为它导致了一些内置不可变类型的问题,解释器喜欢重用实例。例如,允许(1).__class__ = SomethingElse会引起很多问题。您可以在 setter 源代码的大注释中阅读更多内容__class__。(该评论稍微过时了,特别是关于Py_TPFLAGS_IMMUTABLETYPE标记,该标记是在评论撰写后添加的。)
据我所知,这是唯一的问题,并且我认为此后没有添加更多问题。解释器不会积极地重用类的实例,因此只要您不执行类似的操作,并且内存布局兼容,我认为更改对象的类型现在应该有效,即使对于非-堆类型。然而,它并没有得到官方支持,所以即使我目前的看法是正确的,也不能保证它会继续工作。
Py_SET_TYPE仅设置对象的类型指针。它不会进行任何可能需要的重新计数修复。这是一个非常低级的操作。如果旧类和新类都不是堆类型,则不需要额外的引用计数修复,但如果旧类是堆类型,则必须对旧类进行 decref,如果新类是堆类型,则需要对旧类进行 decref 操作。将不得不增加新班级。
如果您需要减少旧类的引用,请确保在更改对象的类并可能增加新类之后执行此操作。
| 归档时间: |
|
| 查看次数: |
306 次 |
| 最近记录: |