-
Notifications
You must be signed in to change notification settings - Fork 760
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Pickle Support #100
Comments
Would this mean implementing For instance, the way pickling works for, might provide some examples. |
For instance, if we take the documentation example for # use pyo3::prelude::*;
# use pyo3::PyRawObject;
#[pyclass]
struct MyClass {
num: i32,
}
#[pymethods]
impl MyClass {
#[new]
fn new(obj: &PyRawObject, num: i32) {
obj.init({
MyClass {
num,
}
});
}
} by default we get the following error when pickling this class, obj = MyClass()
> pickle.dumps(obj)
E TypeError: can't pickle MyClass objects If we now add the fn __getstate__(&self) -> PyResult<(i32)> {
Ok(self.num)
}
fn __setstate__(&mut self, state: i32) -> PyResult<()> {
self.num = state;
Ok(())
} we get another exception,
There is some additional step I must be missing here. |
@rth : this may be related to the fact that PyO3 exposes all classes as part of the |
So by subclassing , to set the Though, I get a segfault occasionally (i.e. it does seem to be random) at exit. For instance when running a pytest session where one test checks pickling,
and there is no backtrace. Will try to investigate it later. |
The segfault likely occurs because subclassing is broken |
How about trying dill? Pickle can't handle lots of pure python serialisation cases. |
Not sure if it's interesting; this snippet just got shared on gitter. https://gist.github.com/ethanhs/fd4123487974c91c7e5960acc9aa2a77 |
I've got a simple struct that I need to deepcopy. I'm trying to figure out how to pickle my struct (after getting the TypeError: cannot pickle error). The gist above shows how to do it for a single member, but I'm too much of a newb to see how to do this with multiple members. I tried
..but get an error "expected one of |
@shaolo1 I would just return the tuple of members:
|
@davidhewitt Thanks. I'll try that if I encounter it again. I got around the problem by just implementing deepcopy in the parent object and handling the copy there so that pickle support was not needed in my rust object. |
I was able to enable pickling by writing the Here is a generic example: pub fn __setstate__(&mut self, state: Vec<u8>) -> PyResult<()> {
*self = deserialize(&state).unwrap();
Ok(())
}
pub fn __getstate__(&self) -> PyResult<Vec<u8>> {
Ok(serialize(&self).unwrap())
}
pub fn __getnewargs__(&self) -> PyResult<(f64, f64)> {
Ok((self.my_first_arg, self.my_second_arg))
} Also, here is a code example for the workaround @shaolo1 mentioned. Cloning for deepcopy may be faster than serializing & deserializing (which I guess is how Python deepcopies normally?), but I haven't tested that. pub fn copy(&self) -> Self {self.clone()}
pub fn __copy__(&self) -> Self {self.clone()}
pub fn __deepcopy__(&self, _memo: &PyDict) -> Self {self.clone()} That'll allow you to return a clone using Edits:
|
Yes, I think you want something like this: pub fn __setstate__(&mut self, state: &PyBytes) -> PyResult<()> {
*self = deserialize(state.as_bytes()).unwrap();
Ok(())
}
pub fn __getstate__<'py>(&self, py: Python<'py>) -> PyResult<&'py PyBytes> {
Ok(PyBytes::new(py, serialize(&self).unwrap()))
}
pub fn __getnewargs__(&self) -> PyResult<(f64, f64)> {
Ok((self.my_first_arg, self.my_second_arg))
} I would also strongly recommend you replace |
Woah, yeah that sped up my round trip serializing and deserializing benchmark by 100x. And thanks for the tip about PyResult errors. I did have to modify pub fn __getstate__<'py>(&self, py: Python<'py>) -> PyResult<&'py PyBytes> {
Ok(PyBytes::new(py, &serialize(&self).unwrap()))
} I also did some benchmarking with my structs regarding the performance of cloning vs. roundtrip pickling and bincode serde, which might be useful to someone:
|
never mind, I've switched to |
You can also use |
there is no way to avoid construct the object before ? I can't understand why they forced to construct the object and THEN set it to specific state. https://peps.python.org/pep-0307 is very hard to read |
If like me you have trouble to use reduce here a very simple example: #[staticmethod]
pub fn deserialize(data: Vec<u8>) -> Self {
Foo {
inner: rmp_serde::from_slice(&data).unwrap(),
}
}
pub fn __reduce__(&self) -> (PyObject, PyObject) {
Python::with_gil(|py| {
py.run_bound("import mylib", None, None).unwrap();
let deserialize = py.eval_bound("mylib.Foo.deserialize", None, None).unwrap();
let data = rmp_serde::to_vec(&self.inner).unwrap;
(deserialize.to_object(py), (data,).to_object(py))
})
} If someone have a way to avoid |
As of right now its not possible to pickle classes created by PyO3.
This feature would be invaluable for situations where some form of persistence would be desireable.
As of right now it has trouble pickling after I call
Otherwise the
.__dict__
attributes are maintained prior to initialization with__new__
The text was updated successfully, but these errors were encountered: