腌制一个包含 __cinit__ 的 cython class :__setstate__ vs __reduce__?
pickle a cython class containing __cinit__ : __setstate__ vs __reduce__?
我正在努力使一些 cython 对象可拾取,并且有一个关于使用 __setstate_
与 __reduce__
的问题。似乎当您 pickle.loads()
一个具有 __setstate__
方法和 __cinit__
方法的对象时,__cinit__
确实会被调用(不同于 __init__
).有没有办法防止这种情况或传递默认参数,或者我应该只使用 __reduce__
?
这里有一个玩具问题来说明(代码修改自 blog)。
在 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
构建后,pickling 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__
被调用....
Person3
中的 __reduce__
方法按预期工作:
p3 = test.Person3('timmy',12)
p_l = pickle.loads(pickle.dumps(p3))
那么有没有办法用__setstate__
腌制Person2
?
在我的实际问题中,类更复杂,使用__setstate__
会更直接,但也许我必须在这里使用__reduce__
?我是 cython 和自定义酸洗的新手(也不太了解 C ……),所以可能会遗漏一些明显的东西……
The purpose of __cinit__
is that it always gets run.
Your __cinit__()
method is guaranteed to be called exactly once.
相比之下,__init__
可以不调用(例如在您的情况下,或者在继承的 classes 中)或多次调用。
总是被调用的 __cinit__
的价值在于,许多 classC 类型的 es 必须以某种方式设置,否则它们将自动无效 - 例如,它们可能期望指向被初始化为 class 持有的一些内存。 (这是导致桌面自动崩溃的那种无效,而不是应该只导致 Python 异常的“Python”无效)。
由于您的玩具示例仅包含 Python 个对象,您最好只使用 __init__
- 没有理由 必须 进行初始化。如果需要,您可以同时使用 __init__
和 __cinit__
,以将正常初始化中必须发生的部分与刚发生的部分分开。
如果您确实选择了使用 __cinit__
,但想从具有不同数量参数的各种上下文中调用它,文档建议
you may find it useful to give the __cinit__()
method *
and **
arguments so that it can accept and ignore extra arguments.
总而言之,使用 __cinit__
进行初始化,以免程序崩溃。接受它将从 __setstate__
调用(因为您不希望程序在使用 __setstate__
后崩溃,对吧?)。将它与 __init__
结合起来进行通常应该发生但不是必需的初始化,有时可能会被覆盖。使用默认参数或 *
和 **
参数使 __cinit__
足够灵活以满足您的需求。
简而言之:使用 __getnewargs_ex__
or __getnewargs__
为 __cinit__
-method 提供所需的参数。
它是如何工作的?创建 Python 对象时,这是一个两步过程:
- 首先,
__new__
用于创建一个未初始化的对象
- 第二步,
__init__
用于初始化第一步创建的对象
pickle
使用稍微不同的算法:
__new__
用于创建未初始化的对象
__setstate__
(不再__init__
)用于初始化第一步创建的对象。
这是有道理的:__init__
与对象的“当前”状态无关。我们不知道 __init__
的参数,即使 __init__
没有参数,它也可能做不必要的工作。
__cinit__
哪里来的戏?当定义 __cinit__
时,Cython 会自动定义一个 __new__
-method(这就是不可能在 cdef
-calls 中手动定义 __new__
-method 的原因),在返回之前调用提供的 __cinit__
方法。在 Person2
示例中,此函数如下所示:
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__
会被 pickle 调用,我们无法阻止,因为无论如何都必须调用 __new__
。
然而,pickle
提供了进一步的挂钩,以获取 __cinit__
-方法所需的信息到 __new__
-方法:__getnewargs_ex__
and __getnewargs__
.
您的 Person2
class 可能如下所示:
%%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__
代替会更有意义。
通常使用 __cinit__
而不是 __init__
来表示 cdef-classes。然而,总的来说,它不是 100% 正确的,当涉及到酸洗时,重要的是要确定 __cinit__
中发生的事情以及 __init__
中发生的事情。
另一个极端,即将整个初始化代码放入 __init__
方法中,很容易解决酸洗问题。但是,组合 __new__
+__init__
不是原子的,可能会调用 __new__
然后在之前使用该对象(或者像 pickling
那样,) __init__
-方法被调用,这可能导致 NULL-pointer-dereferencing 和其他崩溃。
还必须注意,虽然 __cinit__
只执行一次(当执行 __new__
时),但 __init__
可以执行多次(例如 __new__
可以覆盖一个子 class ,这样它总是 returns 相同的单例),这意味着:
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)
是一个可能的 memory-leak,因为 a
可能是一个初始化的指针而不是 NULL
,这仅对 __cinit__
.
有保证
我正在努力使一些 cython 对象可拾取,并且有一个关于使用 __setstate_
与 __reduce__
的问题。似乎当您 pickle.loads()
一个具有 __setstate__
方法和 __cinit__
方法的对象时,__cinit__
确实会被调用(不同于 __init__
).有没有办法防止这种情况或传递默认参数,或者我应该只使用 __reduce__
?
这里有一个玩具问题来说明(代码修改自 blog)。
在 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
构建后,pickling 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__
被调用....
Person3
中的 __reduce__
方法按预期工作:
p3 = test.Person3('timmy',12)
p_l = pickle.loads(pickle.dumps(p3))
那么有没有办法用__setstate__
腌制Person2
?
在我的实际问题中,类更复杂,使用__setstate__
会更直接,但也许我必须在这里使用__reduce__
?我是 cython 和自定义酸洗的新手(也不太了解 C ……),所以可能会遗漏一些明显的东西……
The purpose of __cinit__
is that it always gets run.
Your
__cinit__()
method is guaranteed to be called exactly once.
相比之下,__init__
可以不调用(例如在您的情况下,或者在继承的 classes 中)或多次调用。
总是被调用的 __cinit__
的价值在于,许多 classC 类型的 es 必须以某种方式设置,否则它们将自动无效 - 例如,它们可能期望指向被初始化为 class 持有的一些内存。 (这是导致桌面自动崩溃的那种无效,而不是应该只导致 Python 异常的“Python”无效)。
由于您的玩具示例仅包含 Python 个对象,您最好只使用 __init__
- 没有理由 必须 进行初始化。如果需要,您可以同时使用 __init__
和 __cinit__
,以将正常初始化中必须发生的部分与刚发生的部分分开。
如果您确实选择了使用 __cinit__
,但想从具有不同数量参数的各种上下文中调用它,文档建议
you may find it useful to give the
__cinit__()
method*
and**
arguments so that it can accept and ignore extra arguments.
总而言之,使用 __cinit__
进行初始化,以免程序崩溃。接受它将从 __setstate__
调用(因为您不希望程序在使用 __setstate__
后崩溃,对吧?)。将它与 __init__
结合起来进行通常应该发生但不是必需的初始化,有时可能会被覆盖。使用默认参数或 *
和 **
参数使 __cinit__
足够灵活以满足您的需求。
简而言之:使用 __getnewargs_ex__
or __getnewargs__
为 __cinit__
-method 提供所需的参数。
它是如何工作的?创建 Python 对象时,这是一个两步过程:
- 首先,
__new__
用于创建一个未初始化的对象 - 第二步,
__init__
用于初始化第一步创建的对象
pickle
使用稍微不同的算法:
__new__
用于创建未初始化的对象__setstate__
(不再__init__
)用于初始化第一步创建的对象。
这是有道理的:__init__
与对象的“当前”状态无关。我们不知道 __init__
的参数,即使 __init__
没有参数,它也可能做不必要的工作。
__cinit__
哪里来的戏?当定义 __cinit__
时,Cython 会自动定义一个 __new__
-method(这就是不可能在 cdef
-calls 中手动定义 __new__
-method 的原因),在返回之前调用提供的 __cinit__
方法。在 Person2
示例中,此函数如下所示:
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__
会被 pickle 调用,我们无法阻止,因为无论如何都必须调用 __new__
。
pickle
提供了进一步的挂钩,以获取 __cinit__
-方法所需的信息到 __new__
-方法:__getnewargs_ex__
and __getnewargs__
.
您的 Person2
class 可能如下所示:
%%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__
代替会更有意义。
通常使用 __cinit__
而不是 __init__
来表示 cdef-classes。然而,总的来说,它不是 100% 正确的,当涉及到酸洗时,重要的是要确定 __cinit__
中发生的事情以及 __init__
中发生的事情。
另一个极端,即将整个初始化代码放入 __init__
方法中,很容易解决酸洗问题。但是,组合 __new__
+__init__
不是原子的,可能会调用 __new__
然后在之前使用该对象(或者像 pickling
那样,) __init__
-方法被调用,这可能导致 NULL-pointer-dereferencing 和其他崩溃。
还必须注意,虽然 __cinit__
只执行一次(当执行 __new__
时),但 __init__
可以执行多次(例如 __new__
可以覆盖一个子 class ,这样它总是 returns 相同的单例),这意味着:
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)
是一个可能的 memory-leak,因为 a
可能是一个初始化的指针而不是 NULL
,这仅对 __cinit__
.