python - 腌制一个包含 __cinit__ 的 cython 类:__setstate__ vs __reduce__?
问题描述
我正在努力制作一些 cython 对象,并且对使用__setstate_
vs有疑问__reduce__
。似乎当您pickle.loads()
使用一个__setstate__
方法和一个__cinit__
方法的对象时,__cinit__
DOES 会被调用(不像它是一个__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 ......),所以可能会遗漏一些明显的东西......
解决方案
简而言之:使用__getnewargs_ex__
or__getnewargs__
为__cinit__
-method 提供所需的参数。
它是如何工作的?创建 Python 对象时,这是一个两步过程:
- 一、
__new__
用于创建未初始化的对象 - 第二步,
__init__
用于初始化第一步创建的对象
pickle
使用稍微不同的算法:
__new__
用于创建未初始化的对象__setstate__
(不再__init__
是)用于初始化在第一步中创建的对象。
这是有道理的:__init__
与对象的“当前”状态无关。我们不知道参数__init__
,即使__init__
没有参数,也可能做不必要的工作。
哪来__cinit__
的戏?__cinit__
定义时,Cython 会自动定义一个__new__
-method(这就是不可能在 -calls 中手动定义 -method 的原因__new__
) ,它会在返回之前cdef
调用提供的-method。__cinit__
在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__
会被pickle调用,我们无法阻止它,因为__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__
。
通常使用__cinit__
而不是__init__
用于 cdef 类。然而,一般来说,这不是 100% 正确的,当涉及到酸洗时,重要的是要确定发生了__cinit__
什么以及发生了什么__init__
。
另一个极端,即将整个初始化代码放入__init__
- 方法中,很容易解决酸洗问题。但是,组合__new__
+__init__
不是原子的,它可能会__new__
被调用,然后在调用之前(或者像pickling
那样)使用对象__init__
,这可能会导致 NULL 指针取消引用和其他崩溃。
还必须注意,while__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__
.
推荐阅读
- c# - 部署项目时无法访问“wwwroot”中的文件
- rust - 有效地检查 vec 中的每个项目与其他项目并进行变异
- css - 可以获取/设置字符的宽度吗?
- python-3.x - 在任何子目录中找不到任何匹配版本“8”的 cudnn.h
- php - 如何知道 Post 请求传递了哪些值
- java - 您如何修改非注释枚举的默认枚举(反)序列化但在杰克逊中保留标准行为(@JsonProperty/@JsonValue/...)?
- sql - 将 VirtualBox 上的 Oracle 来宾连接到 macOS 主机
- azure-pipelines - 将一些工作项字段映射到评论
- xslt - Saxon.Api.StaticError: 'xsl:import-schema requires Saxon-EE' with .net API
- java - 有没有办法在 AtomicReference 上设置锁定