最近我在基于C的python扩展中遇到了一个问题,试图在不调用其构造函数的情况下实例化对象 - 这是扩展的要求。
用于创建实例的类是动态获取的:在某些时候,我有一个实例x
,我希望用它来创建其他实例,所以我存储x.__class__
以供以后使用 - 让这个值为klass
。
稍后,我调用PyInstance_NewRaw(klass, PyDict_New())
,然后问题出现了。似乎如果klass
是旧式类,则该调用的结果是所需的新实例。但是,如果它是一个新样式的类,结果为NULL,引发的异常是:
SystemError:../ Objectss / classobject.c:521:内部函数的错误参数
为了记录,我使用的是Python版本2.7.5
。谷歌搜索,我观察到只有一个人寻找解决方案(在我看来,他正在做一个解决方法,但没有详细说明。)
对于记录#2:扩展程序正在创建的实例是这些相同x
个实例的代理 - x.__class__
和x.__dict__
已知,因此扩展程序正在生成基于__class__
的新实例(使用上述C函数)并将相应的__dict__
设置为新实例(那些__dict__
具有进程间共享内存数据)。第二次调用实例__init__
不仅在概念上有问题(第一次:它的状态已经知道,第二次:ctors的预期行为是每个实例应该只调用一次),这也是不切实际的,因为扩展无法找出参数及其为系统中的每个实例调用__init__()
的顺序。此外,更改系统中每个类的__init__
,其实例可能是代理并让他们意识到他们将要经历的代理机制在概念上是有问题的(他们不应该知道它)并且不切实际。 / p>
所以,我的问题是:无论实例的类风格如何,如何执行PyInstance_NewRaw
的相同行为?
答案 0 :(得分:2)
新式类的类型不是instance
,而是类本身。因此,PyInstance_*
方法对于新式类甚至没有意义。
事实上,the documentation明确解释了这一点:
请注意,此处描述的类对象表示旧式类,它们将在Python 3中消失。为扩展模块创建新类型时,您将需要使用类型对象(第Type Objects部分)。 / p>
因此,您必须编写代码来检查klass
是旧式还是新式类,并针对每种情况执行适当的操作。旧式类的类型为PyClass_Type
,而新式类的类型为PyType_Type
或自定义元类。
同时,对于新式类,没有直接等效的PyInstance_NewRaw
。或者,相反,直接等效调用其tp_alloc
槽然后添加一个dict将给你一个非功能类。您可以尝试复制所有其他适当的工作,但这将是棘手的。或者,您可以使用tp_new
,但如果类(或其任何基础)中存在自定义__new__
函数,则会执行错误操作。有关一些想法,请参阅#5180中被拒绝的补丁。
但实际上,你想要做的事情可能不是一个好主意。也许如果你解释了为什么这是一个要求,以及你想要做什么,那么就有更好的方法去做。
如果目标是通过创建一个新的未初始化的类实例来构建对象,然后从初始化的原型复制其_dict__
,那么我认为这个解决方案更容易适用于您:
__class__
是一个可写属性。所以(在Python中显示它; C API基本相同,只是更冗长,我可能搞砸了某处的引用计数):
class NewStyleDummy(object):
pass
def make_instance(cls, instance_dict):
if isinstance(cls, types.ClassType):
obj = do_old_style_thing(cls)
else:
obj = NewStyleDummy()
obj.__class__ = cls
obj.__dict__ = instance_dict
return obj
新对象将是cls
的一个实例 - 特别是它将具有相同的类字典,包括MRO,元类等。
如果cls
具有构造所需的元类,或自定义__new__
方法或__slots__
...但是您的复制设计__dict__
,则无效无论如何,在这些情况下,1}}没有任何意义。我相信,在任何可能有效的情况下,这个简单的解决方案都可行。
首先调用cls.__new__
似乎是一个很好的解决方案,但实际上并非如此。让我解释一下背景。
执行此操作时:
foo = Foo(1, 2)
(其中Foo
是一个新式的类),它被转换成类似这样的伪代码:
foo = Foo.__new__(1, 2)
if isinstance(foo, Foo):
foo.__init__(1, 2)
问题在于,如果Foo
或其中一个基础定义了__new__
方法,它将期望从构造函数调用中获取参数,就像__init__
方法一样将
正如你在问题中解释的那样,你不知道构造函数调用参数 - 实际上,这是你不能首先调用普通__init__
方法的主要原因。因此,您也无法致电__new__
。
__new__
的基本实现接受并忽略它给出的任何参数。所以,如果你的课程都没有__new__
覆盖或__metaclass__
,你就会碰巧这样做,因为object.__new__
中有一个怪癖(怪癖在Python中有所不同) 3.x,顺便说一下)。但这些是以前的解决方案可以处理的完全相同的情况,并且该解决方案的工作原因更为明显。
换句话说:之前的解决方案取决于定义__new__
的人,因为它从不调用__new__
。此解决方案取决于定义__new__
的任何人,因为它使用错误的参数调用__new__
。