2

最近,我在尝试实例化对象而不调用其构造函数时在基于 C 的 python 扩展中遇到了一个问题——这是扩展的要求

用于创建实例的类是动态获取的:在某些时候,我有一个实例x,我希望使用它的类来创建其他实例,所以我存储x.__class__以供以后使用——让这个值成为klass.

稍后,我调用PyInstance_NewRaw(klass, PyDict_New()),然后问题出现了。似乎 ifklass是一个旧式类,该调用的结果是所需的新实例。但是,如果是新式类,则结果为 NULL,引发的异常为:

SystemError:../Objects/classobject.c:521:内部函数的参数错误

作为记录,我使用的是 Python 版本2.7.5。谷歌搜索,我发现只有一个人在寻找解决方案(在我看来,他正在做一个解决方法,但没有详细说明)。

对于记录#2:扩展正在创建的实例是这些相同x实例的代理 -x.__class__x.__dict__是已知的,因此扩展基于__class__(使用上述 C 函数)生成新实例并将各自设置为__dict__新实例实例(那些__dict__具有进程间共享内存数据)。第二次调用实例不仅在概念上存在问题__init__(首先:它的状态已经知道,其次:ctor 的预期行为是它们应该为每个实例只调用一次),这也是不切实际的,因为扩展不能找出__init__()系统中每个实例的参数及其调用顺序。此外,改变__init__系统中每个类的实例可能是代理,并让他们知道他们将受到代理机制的影响在概念上是有问题的(他们不应该知道它)并且不切实际。

所以,我的问题是:PyInstance_NewRaw无论实例的类风格如何,如何执行相同的行为?

4

1 回答 1

2

新式类的类型不是instance,而是类本身。因此,这些PyInstance_*方法对于新式类甚至没有意义。

事实上,文档明确解释了这一点:

请注意,此处描述的类对象代表旧式类,它将在 Python 3 中消失。为扩展模块创建新类型时,您将需要使用类型对象(类型对象部分)。

因此,您必须编写代码来检查klass是旧式还是新式类,并为每种情况做适当的事情。旧式类的类型是PyClass_Type,而新式类的类型要么是PyType_Type,要么是自定义元类。

PyInstance_NewRaw同时,对于新式类没有直接的等价物。或者,更确切地说,直接等价物——调用它的tp_alloc槽然后添加一个字典——会给你一个非函数类。您可以尝试复制所有其他适当的工作,但这会很棘手。或者,您可以使用,但如果类(或其任何基类)中tp_new有自定义函数,那将做错事。有关一些想法,请参阅#5180__new__中被拒绝的补丁。

但实际上,您首先尝试做的可能不是一个好主意。也许如果您解释了为什么这是一项要求,以及您正在尝试做什么,那么会有更好的方法来做到这一点。


如果目标是通过创建一个新的未初始化的类实例来构建对象,然后_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__无论如何都没有任何意义。我相信,在任何情况下,任何事情都可能奏效,这个简单的解决方案会奏效。


呼叫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__override 或 a __metaclass__,那么您将碰巧侥幸逃脱,因为其中有一个怪癖object.__new__(顺便说一下,这是一个在 Python 3.x 中工作方式不同的怪癖)。但这些与以前的解决方案可以处理的情况完全相同,而且该解决方案的工作原理更明显。

换句话说:前面的解决方案依赖于没有人定义__new__,因为它从不调用__new__. 这个解决方案依赖于没有人定义__new__,因为它__new__使用错误的参数调用。

于 2013-09-12T06:50:59.237 回答