首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >选择包含__cinit__:__setstate__和__reduce__的cython类?

选择包含__cinit__:__setstate__和__reduce__的cython类?
EN

Stack Overflow用户
提问于 2020-09-25 18:20:59
回答 2查看 933关注 0票数 4

我正在努力使一些cython对象成为可选对象,并有一个关于使用__setstate___reduce__的问题。当您使用pickle.loads()方法和__cinit__方法调用对象时,似乎确实会调用__cinit__ (如果它是__init__的话)。是否有办法防止这种情况或传递默认参数,还是应该只使用__reduce__

下面是一个需要说明的玩具问题(从这个博客修改的代码)。

test.pyx中,我有三个类:

代码语言:javascript
运行
复制
cdef class Person:
    cdef public str name
    cdef public int age

    def __init__(self,name,age):
        print('in Person.__init__')
        self.name = name 
        self.age = age 

    def __getstate__(self):
        return (self.name, self.age,)

    def __setstate__(self, state):
        name, age = state
        self.name = name
        self.age = age

cdef class Person2:
    cdef public str name
    cdef public int age

    def __cinit__(self,name,age):
        print('in Person2.__cinit__')
        self.name = name 
        self.age = age 

    def __getstate__(self):
        return (self.name, self.age,)

    def __setstate__(self, state):
        name, age = state
        self.name = name
        self.age = age

cdef class Person3:
    cdef public str name
    cdef public int age

    def __cinit__(self,name,age):
        print('in Person3.__cinit__')
        self.name = name 
        self.age = age 

    def __reduce__(self):
        return (newPerson3,(self.name, self.age))

def newPerson3(name,age):
    return Person3(name,age)

使用python setup.py build_ext --inplace构建之后,Person按预期工作(因为__init__没有被调用):

代码语言:javascript
运行
复制
import test 
import pickle 

p = test.Person('timmy',12)
p_l = pickle.loads(pickle.dumps(p))

酸洗Person2失败:

代码语言:javascript
运行
复制
p2 = test.Person2('timmy',12)
p_l = pickle.loads(pickle.dumps(p2))

使用

代码语言:javascript
运行
复制
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "test.pyx", line 25, in test.Person2.__cinit__
    print('in Person2.__cinit__')
TypeError: __cinit__() takes exactly 2 positional arguments (0 given)

所以__cinit__接到电话..。

__reduce__方法在Person3中按预期工作:

代码语言:javascript
运行
复制
p3 = test.Person3('timmy',12)
p_l = pickle.loads(pickle.dumps(p3))

那么,是否有一种使用__setstate__来腌制Person2的方法?

在我的实际问题中,类更复杂,使用__setstate__会更简单,但也许我必须在这里使用__reduce__?我刚接触过cython和定制的泡菜(也不太了解C.),所以可能缺少一些明显的东西.

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2020-09-26 14:15:21

简而言之:使用__getnewargs_ex____getnewargs____cinit__-method提供所需的参数。

它怎麽工作?创建Python对象时,这是一个两个步骤的过程:

  • 首先,__new__用于创建未初始化的对象。
  • 在第二步中,__init__用于初始化在第一步中创建的对象。

pickle使用的算法略有不同:

  • __new__用于创建未初始化的对象。
  • __setstate__ (不再是__init__)用于初始化在第一步中创建的对象。

这是有意义的:__init__与对象的“当前”状态无关。我们不知道__init__的参数,即使__init__没有参数,也可能做不必要的工作。

__cinit__从何而来?定义__cinit__时,Cython会自动定义__new__-method (这就是为什么不可能在cdef-calls中手动定义__new__-method ),后者在返回之前调用提供的__cinit__-method。在Person2-example中,此函数如下所示:

代码语言:javascript
运行
复制
static PyObject *__pyx_tp_new_4test_Person2(PyTypeObject *t, PyObject *a, PyObject *k) {
  struct __pyx_obj_4test_Person2 *p;
  PyObject *o;
  if (likely((t->tp_flags & Py_TPFLAGS_IS_ABSTRACT) == 0)) {
    o = (*t->tp_alloc)(t, 0);
  } else {
    o = (PyObject *) PyBaseObject_Type.tp_new(t, __pyx_empty_tuple, 0);
  }
  if (unlikely(!o)) return 0;
  p = ((struct __pyx_obj_4test_Person2 *)o);
  p->name = ((PyObject*)Py_None); Py_INCREF(Py_None);
  if (unlikely(__pyx_pw_4test_7Person2_1__cinit__(o, a, k) < 0)) goto bad;
  return o;
  bad:
  Py_DECREF(o); o = 0;
  return NULL;
}

if (unlikely(__pyx_pw_4test_7Person2_1__cinit__(o, a, k) < 0)) goto bad;是调用__cinit__的行。

有了以上这些,我们就清楚了为什么__cinit__会被泡菜所调用,而且我们不能阻止它,因为无论如何都必须调用__new__

然而,pickle提供了更多的钩子来将__cinit__-method所需的信息获取到__new__-method:__getnewargs_ex____getnewargs__

您的Person2类可以如下所示:

代码语言:javascript
运行
复制
%%cython
cdef class Person2:
    cdef public str name
    cdef public int age
    
    def __cinit__(self, name, age):
        self.name=name
        self.age=age

    def __getnewargs_ex__(self):
        return (self.name, self.age),{}

    def __getstate__(self):
        return ()
    
    def __setstate__(self, state):
        pass

而现在

代码语言:javascript
运行
复制
p2 = test.Person2('timmy',12)
p_l = pickle.loads(pickle.dumps(p2))

确实成功了!

这是一个玩具例子,没有什么意义,因此:

  • __getstate____setstate__只是虚拟的,因为所有需要的信息都是由__cinit__提供的,通常情况并非如此。
  • 在本例中,__cinit__没有多大意义,使用__init__会更有意义。

对于cdef类,人们通常使用__cinit__而不是__init__。然而,一般来说,这并不是100%正确的,当酸洗涉及,这是重要的是决定什么正在发生在__cinit__和正在发生什么在__init__

另一个极端,即将整个初始化代码放入__init__-method中,很容易解决泡菜问题。但是,__new__+__init__组合并不是原子的,有可能调用__new__,然后在调用__init__-method之前(或者像pickling一样)使用对象,这可能会导致空指针取消引用和其他崩溃。

还必须注意,虽然__cinit__只执行一次(执行__new__时),但__init__可以多次执行(例如,__new__可以被覆盖,其方式总是返回相同的单例),这意味着:

代码语言:javascript
运行
复制
cdef class A:
    cdef char *a
    def __cinit__(self):
       a=<char*> malloc(1)

是可以的,而__init__中的相同代码

代码语言:javascript
运行
复制
cdef class A:
    cdef char *a
    def __init__(self):
       a=<char*> malloc(1)

是一个可能的内存泄漏,因为a可能是一个初始化指针,而不是NULL,这是只为__cinit__所保证的。

票数 4
EN

Stack Overflow用户

发布于 2020-09-26 09:55:34

就跑了。

保证只调用一次__cinit__()方法。

相反,可以不调用__init__ (例如,在您的情况下,或者在继承的类中)或多次调用。

经常调用__cinit__的值是,许多具有C类型的类必须以某种方式设置,否则它们就会自动失效--例如,它们可能希望指针被初始化到该类所保存的某个内存。(这是一种导致桌面自动崩溃的无效类型,而不是导致Python异常的"Python“无效)。

因为您的玩具示例只包含Python对象,所以使用__init__可能会更好--没有理由必须初始化它。如果需要,您可以同时拥有__init____cinit__,以便将必须发生的部分与在正常初始化过程中刚刚发生的部分分开。

如果您确实选择使用__cinit__,但希望使用不同数量的参数从不同的上下文中调用它,那么文档建议

您可能会发现提供__cinit__()方法***参数非常有用,这样它就可以接受和忽略额外的参数。

总之,使用__cinit__来完成程序不崩溃所必须进行的初始化。请接受__setstate__将调用它(因为您不希望程序在使用__setstate__后崩溃,对吗?)将它与__init__结合起来进行通常应该发生的初始化,但这不是必需的,有时可能会被覆盖。使用默认参数或***参数使__cinit__足够灵活,以满足您的需要。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/64069471

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档