我正在努力使一些cython对象成为可选对象,并有一个关于使用__setstate_
和__reduce__
的问题。当您使用pickle.loads()
方法和__cinit__
方法调用对象时,似乎确实会调用__cinit__
(如果它是__init__
的话)。是否有办法防止这种情况或传递默认参数,还是应该只使用__reduce__
下面是一个需要说明的玩具问题(从这个博客修改的代码)。
在test.pyx
中,我有三个类:
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__
没有被调用):
import test
import pickle
p = test.Person('timmy',12)
p_l = pickle.loads(pickle.dumps(p))
酸洗Person2
失败:
p2 = test.Person2('timmy',12)
p_l = pickle.loads(pickle.dumps(p2))
使用
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
中按预期工作:
p3 = test.Person3('timmy',12)
p_l = pickle.loads(pickle.dumps(p3))
那么,是否有一种使用__setstate__
来腌制Person2
的方法?
在我的实际问题中,类更复杂,使用__setstate__
会更简单,但也许我必须在这里使用__reduce__
?我刚接触过cython和定制的泡菜(也不太了解C.),所以可能缺少一些明显的东西.
发布于 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中,此函数如下所示:
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
类可以如下所示:
%%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
而现在
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__
可以被覆盖,其方式总是返回相同的单例),这意味着:
cdef class A:
cdef char *a
def __cinit__(self):
a=<char*> malloc(1)
是可以的,而__init__
中的相同代码
cdef class A:
cdef char *a
def __init__(self):
a=<char*> malloc(1)
是一个可能的内存泄漏,因为a
可能是一个初始化指针,而不是NULL
,这是只为__cinit__
所保证的。
发布于 2020-09-26 09:55:34
保证只调用一次
__cinit__()
方法。
相反,可以不调用__init__
(例如,在您的情况下,或者在继承的类中)或多次调用。
经常调用__cinit__
的值是,许多具有C类型的类必须以某种方式设置,否则它们就会自动失效--例如,它们可能希望指针被初始化到该类所保存的某个内存。(这是一种导致桌面自动崩溃的无效类型,而不是导致Python异常的"Python“无效)。
因为您的玩具示例只包含Python对象,所以使用__init__
可能会更好--没有理由必须初始化它。如果需要,您可以同时拥有__init__
和__cinit__
,以便将必须发生的部分与在正常初始化过程中刚刚发生的部分分开。
如果您确实选择使用__cinit__
,但希望使用不同数量的参数从不同的上下文中调用它,那么文档建议
您可能会发现提供
__cinit__()
方法*
和**
参数非常有用,这样它就可以接受和忽略额外的参数。
总之,使用__cinit__
来完成程序不崩溃所必须进行的初始化。请接受__setstate__
将调用它(因为您不希望程序在使用__setstate__
后崩溃,对吗?)将它与__init__
结合起来进行通常应该发生的初始化,但这不是必需的,有时可能会被覆盖。使用默认参数或*
和**
参数使__cinit__
足够灵活,以满足您的需要。
https://stackoverflow.com/questions/64069471
复制相似问题