Python PyInstance_NewRaw()具有新旧样式的类
最近,我在尝试实例化对象而不调用其构造函数时遇到了一个基于C的python扩展中的问题——这是扩展的一个要求 用于创建实例的类是动态获取的:在某个时刻,我有一个实例Python PyInstance_NewRaw()具有新旧样式的类,python,python-2.7,python-extensions,Python,Python 2.7,Python Extensions,最近,我在尝试实例化对象而不调用其构造函数时遇到了一个基于C的python扩展中的问题——这是扩展的一个要求 用于创建实例的类是动态获取的:在某个时刻,我有一个实例x,我希望使用它的类来创建其他实例,因此我存储x.\uuuu class\uuuu,以备以后使用--让这个值为klass 稍后,我调用PyInstance_NewRaw(klass,PyDict_New()),然后问题就出现了。如果klass是一个旧式类,那么调用的结果就是所需的新实例。但是,如果它是一个新样式的类,则结果为NULL,
x
,我希望使用它的类来创建其他实例,因此我存储x.\uuuu class\uuuu
,以备以后使用--让这个值为klass
稍后,我调用PyInstance_NewRaw(klass,PyDict_New())
,然后问题就出现了。如果klass
是一个旧式类,那么调用的结果就是所需的新实例。但是,如果它是一个新样式的类,则结果为NULL,引发的异常为:
SystemError:../Objects/classobject.c:521:内部函数的参数错误
作为记录,我使用的是Python版本2.7.5
。在谷歌上搜索,我发现只有一个人在寻找解决方案(在我看来,他在做一个变通方案,但没有详细说明)
对于记录#2:扩展正在创建的实例是这些相同的x
实例的代理--x.\uuuuu-class\uuuuuuuu
和x.\uuu-dict\uuuuuuu
是已知的,因此扩展正在基于\uuu-class\uuuuuu
生成新实例(使用前面提到的C函数)以及将相应的\uuuuu dict\uuuu
设置为新实例(那些\uuuuu dict\uuuu
具有进程间共享内存数据)。第二次调用实例的\uuuu init\uuuu
不仅在概念上有问题(第一:它的状态已经知道,第二:ctor的预期行为是每个实例只调用一次),而且也不切实际,因为扩展无法计算参数及其顺序,无法为系统中的每个实例调用\uuu init\uu()
。此外,更改系统中每个类(其实例可能是代理)的\uuuuuu init\uuuuu
,并让它们意识到它们将受到代理机制的约束,这在概念上是有问题的(它们不应该知道),而且是不切实际的
因此,我的问题是:无论实例的类样式如何,如何执行相同的
PyInstance\u NewRaw
行为?新样式类的类型不是instance
,而是类本身。因此,PyInstance.*
方法对于新样式的类甚至没有意义
事实上,他明确地解释了这一点:
请注意,这里描述的类对象表示旧式的类,在Python3中会消失。为扩展模块创建新类型时,您需要使用类型对象(第节)
因此,您必须编写代码来检查klass
是旧式类还是新式类,并针对每种情况执行适当的操作。旧样式类的类型是PyClass\u类型
,而新样式类的类型是PyType\u类型
,或者是自定义元类
同时,对于新样式的类,没有直接等价于PyInstance\u NewRaw
。或者,更确切地说,直接调用其tp_alloc
插槽,然后添加一个dict将为您提供一个非函数类。您可以尝试复制所有其他适当的工作,但这将是棘手的。或者,您可以使用tp\u new
,但如果类(或其任何基)中有自定义的\uuu new\uuu
函数,则这将是错误的。有关一些想法,请参阅中被拒绝的修补程序
但实际上,你想做的一开始可能不是个好主意。也许如果你解释了为什么这是一个要求,以及你想做什么,会有更好的方法去做
如果目标是通过创建新的未初始化的类实例来构建对象,然后从初始化的原型复制其
\u dict\uuu
,那么我认为有一个更简单的解决方案适合您:
\uuuu class\uuuu
是一个可写属性。因此(在Python中显示它;C API基本上是相同的,只是要详细得多,我可能会在某个地方搞砸引用计数):
新对象将是cls
的一个实例,特别是,它将具有相同的类字典,包括MRO、元类等
如果cls
有一个构造它所需的元类,或者有一个自定义的\uuuuuuuu新的\uuuuuuu方法,或者\uuuuu插槽\uuuuuuuu
,那么这将不起作用。但是,在这些情况下,您在\uuuuuuu dict\uuuuuuu
上进行复制的设计无论如何都没有任何意义。我相信在任何可能奏效的情况下,这个简单的解决方案都会奏效
一开始打电话似乎是个好办法,但实际上不是。让我解释一下背景
执行此操作时:
foo = Foo(1, 2)
(其中,Foo
是一个新样式的类),它被转换为类似以下伪代码的内容:
foo = Foo.__new__(1, 2)
if isinstance(foo, Foo):
foo.__init__(1, 2)
问题是,如果Foo
或它的一个基定义了\uuuuuuuuuuuuuuuu新方法,它将期望从构造函数调用中获取参数,就像\uuuuuuuuuu init\uuuuuuuuuuuuuuuuuu
方法一样
正如您在问题中所解释的,实际上您不知道构造函数调用参数,这是您无法首先调用普通\uuuuu init\uuuu
方法的主要原因。因此,您也不能调用\uuuuu new\uuuu
\uuuuu new\uuuu
的基本实现接受并忽略给定的任何参数。因此,如果没有一个类具有\uuuuu new\uuuuuu
重写或\uuuuuu元类\uuuuuu
,那么由于对象中的一个怪癖,您将碰巧侥幸逃脱。但这些都是前一个解决方案可以处理的完全相同的情况,而该解决方案之所以有效,原因更为明显
换言之,就是
foo = Foo.__new__(1, 2)
if isinstance(foo, Foo):
foo.__init__(1, 2)