Replies: 2 comments
-
OK, after weeks of fumbling around with unfortunately little pre-existing information to go on I have managed to get something working. QuestionObjectiveI have the following complex enum defined in Rust and ported to Python with PyO3 and I want it to be pickleable. #[pyclass(module = "mymodule.rs", eq)]
#[derive(Clone, Debug, PartialEq)]
pub enum MyEnum {
Single(i32, f64),
Fields{val: f64, check: Option<bool>, on: i8},
} AnswerMethodDefine a #[derive(Clone, Debug, FromPyObject, IntoPyObject)]
enum MyEnumNewArgs {
Single(i32, f64),
Fields(f64, Option<bool>, i8)
}
#[pymethods]
impl MyEnum {
#[new]
fn new_py(args: MyEnumNewArgs) -> MyEnum {
match args {
MyEnumNewArgs::Single(a, b) => MyEnum::Single(a, b),
MyEnumNewArgs::Fields(a, b, c) => MyEnum::Fields{val: a, check: b, on: c},
}
}
fn __getnewargs__<'py>(&self, _py: Python<'py>) -> MyEnumNewArgs {
match self {
MyEnum::Single(a, b) => MyEnumNewArgs::Single(*a, *b),
MyEnum::Fields{val: a, check: b, on: c} => MyEnumNewArgs::Fields(*a, b.clone(), *c),
}
}
} This almost works. But, due to the nested class structure of complex enums (for example(#5250 (reply in thread))) they seemed to exhibit similar issues with pickling NamedTuples, e.g. (https://stackoverflow.com/questions/4677012/python-cant-pickle-type-x-attribute-lookup-failed) Attempting to now pickle these objects will result in the error:
If the namespace for the module is monkey-patched at top level import mymodule.rs
mymodule.rs.MyEnum_Single = mymodule.rs.MyEnum.Single
mymodule.rs.MyEnum_Fields = mymodule.rs.MyEnum.Fields Then pickling will succeed. single = MyEnum.Single(3, 4)
fields = MyEnum.Fields(3, None, 2)
import pickle
for obj in [single, fields]:
p = pickle.dumps(obj)
l = pickle.loads(p)
assert obj == l # WORKS: AT BLOODY LAST! GotchasIf any of your variants have only 1 arguments then impl<'py> IntoPyObject<'py> for MyEnumNewArgs {
type Target = PyTuple;
type Output = Bound<'py, Self::Target>;
type Error = std::convert::Infallible;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
match self {
MyEnumNewArgs::Single(a) => Ok((a,).into_pyobject(py).unwrap()),
MyEnumNewArgs::Fields(a, b, c) => Ok((a,b,c).into_pyobject(py).unwrap()),
}
}
} I also do not know if monkey-patching the namespace has any other unwanted consequences. FailuresI have not figured out how to do this for empty variants. I suppose I might need to manually implement a #[pyclass(module = "mymodule.rs", eq)]
#[derive(Clone, Debug, PartialEq)]
pub enum MyEnum {
Null(),
Fields{val: f64, check: Option<bool>, on: i8},
}
#[derive(Clone, Debug)] // need to implement FromPyObject and IntoPyObject
enum MyEnumNewArgs {
Null(),
Fields(f64, Option<bool>, i8)
} |
Beta Was this translation helpful? Give feedback.
-
Ok at this point I'm just updating this as my own reference. Update
For the complex enum the pyclass is frozen meaning the state setting does not acquire the mutable reference. See error above, A)Simple Enum #[pyclass(eq)]
#[derive(Copy, Clone, PartialEq)]
pub enum MyEnum2 {
Value0 = 0,
Value1 = 1,
}
#[pymethods]
impl MyEnum2 {
#[new]
fn new_py(item: usize) -> MyEnum {
match item {
_ if item == MyEnum2::Value0 as usize => MyEnum2::Value0,
_ if item == MyEnum2::Value1 as usize => MyEnum2::Value1,
_ => MyEnum2::Value0,
}
}
pub fn __getnewargs__<'py>(&self) -> PyResult<(usize,)> {
Ok((*self as usize,))
}
} B)Complex and each variant has a unique set of inputs >= 2 #[pyclass(eq)]
#[derive(Clone, Debug, PartialEq)]
enum MyEnum {
A(u32, f64),
B(u32, u32),
}
#[derive(FromPyObject, IntoPyObject)]
enum MyEnumNewArgs {
A(u32, f64),
B(u32, u32),
}
#[pymethods]
impl MyEnum {
fn __getnewargs__(&self) -> MyEnumNewArgs {
match self {
MyEnum::A(x, y) => MyEnumNewArgs::A(*x, *y),
MyEnum::B(x, y) => MyEnumNewArgs::B(*x, *y),
}
}
#[new]
fn new_py(args: MyEnumNewArgs) -> MyEnum {
match args {
MyEnumNewArgs::A(x, y) => MyEnum::A(x, y),
MyEnumNewArgs::B(x, y) => MyEnum::B(x, y),
}
}
} mymodule.rs.MyEnum_A = mymodule.rs.MyEnum.A
mymodule.rs.MyEnum_B = mymodule.rs.MyEnum.B C)Complex and each variant has a unique set of inputs >= 1 #[pyclass(eq)]
#[derive(Clone, Debug, PartialEq)]
enum MyEnum {
A(u32),
B(u32, u32),
}
#[derive(FromPyObject)]
enum MyEnumNewArgs {
A(u32),
B(u32, u32),
}
impl<'py> IntoPyObject<'py> for MyEnumNewArgs {
type Target = PyTuple;
type Output = Bound<'py, Self::Target>;
type Error = std::convert::Infallible;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
match self {
MyEnumNewArgs::A(x) => Ok((x,).into_pyobject(py).unwrap()),
MyEnumNewArgs::B(x, y) => Ok((x,y).into_pyobject(py).unwrap()),
}
}
}
#[pymethods]
impl MyEnum {
fn __getnewargs__(&self) -> MyEnumNewArgs {
match self {
MyEnum::A(x) => MyEnumNewArgs::A(*x),
MyEnum::B(x, y) => MyEnumNewArgs::B(*x, *y),
}
}
#[new]
fn new_py(args: MyEnumNewArgs) -> MyEnum {
match args {
MyEnumNewArgs::A(x) => MyEnum::A(x),
MyEnumNewArgs::B(x, y) => MyEnum::B(x, y),
}
}
} mymodule.rs.MyEnum_A = mymodule.rs.MyEnum.A
mymodule.rs.MyEnum_B = mymodule.rs.MyEnum.B D)Complex and each variant has a unique set of inputs >= 0 #[pyclass(module= "rateslib.rs", eq)]
#[derive(Clone, Debug, PartialEq)]
enum MyEnum {
A(u32),
B(),
}
enum MyEnumNewArgs {
A(u32),
B(),
}
impl<'py> IntoPyObject<'py> for MyEnumNewArgs {
type Target = PyTuple;
type Output = Bound<'py, Self::Target>;
type Error = std::convert::Infallible;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
match self {
MyEnumNewArgs::A(x) => Ok((x,).into_pyobject(py).unwrap()),
MyEnumNewArgs::B() => Ok(PyTuple::empty(py)),
}
}
}
impl<'py> FromPyObject<'py> for MyEnumNewArgs {
fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
let ext: PyResult<(u32,)> = ob.extract();
match ext {
Ok(v) => Ok(MyEnumNewArgs::A(v.0)),
Err(_) => Ok(MyEnumNewArgs::B()),
}
}
}
#[pymethods]
impl MyEnum {
fn __getnewargs__(&self) -> MyEnumNewArgs {
match self {
MyEnum::A(x) => MyEnumNewArgs::A(*x),
MyEnum::B() => MyEnumNewArgs::B(),
}
}
#[new]
fn new_py(args: MyEnumNewArgs) -> MyEnum {
match args {
MyEnumNewArgs::A(x) => MyEnum::A(x),
MyEnumNewArgs::B() => MyEnum::B(),
}
}
} mymodule.rs.MyEnum_A = mymodule.rs.MyEnum.A
mymodule.rs.MyEnum_B = mymodule.rs.MyEnum.B E)When a complex enum contains variants with the same arg signatures the Using a fields type variant allows Python users to effectively ignore the item, although it makes it messier for Rust code. #[pyclass(eq)]
#[derive(Clone, Debug, PartialEq)]
enum MyEnum {
A(u32, u8),
#[pyo3(constructor = (_pickle_id=1))]
B{ _pickle_id: u8},
C(u8),
}
enum MyEnumNewArgs {
A(u32, u8),
B(u8),
C(u8),
}
impl<'py> IntoPyObject<'py> for MyEnumNewArgs {
type Target = PyTuple;
type Output = Bound<'py, Self::Target>;
type Error = std::convert::Infallible;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
match self {
MyEnumNewArgs::A(x, _) => Ok((x,0).into_pyobject(py).unwrap()),
MyEnumNewArgs::B(_) => Ok((1,).into_pyobject(py).unwrap()),
MyEnumNewArgs::C(_) => Ok((2,).into_pyobject(py).unwrap()),
}
}
}
impl<'py> FromPyObject<'py> for MyEnumNewArgs {
fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
let ext: PyResult<(u32, u8)> = ob.extract();
if ext.is_ok() {
return Ok(MyEnumNewArgs::A(ext.unwrap().0, 0))
}
let ext: (u8,) = ob.extract()?;
match ext {
(1,) => Ok(MyEnumNewArgs::B(1)),
(2,) => Ok(MyEnumNewArgs::C(2)),
_ => panic!("Undefined"),
}
}
}
#[pymethods]
impl MyEnum {
fn __getnewargs__(&self) -> MyEnumNewArgs {
match self {
MyEnum::A(x, _) => MyEnumNewArgs::A(*x, 0),
MyEnum::B{ _pickle_id: _ } => MyEnumNewArgs::B(1),
MyEnum::C(_) => MyEnumNewArgs::C(2),
}
}
#[new]
fn new_py(args: MyEnumNewArgs) -> MyEnum {
match args {
MyEnumNewArgs::A(x, _) => MyEnum::A(x, 0),
MyEnumNewArgs::B(_) => MyEnum::B{ _pickle_id: 1 },
MyEnumNewArgs::C(_) => MyEnum::C(2),
}
}
} mymodule.rs.MyEnum_A = mymodule.rs.MyEnum.A
mymodule.rs.MyEnum_B = mymodule.rs.MyEnum.B
mymodule.rs.MyEnum_C = mymodule.rs.MyEnum.C |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
PyO3 gives two categorisations for using
#[pyclass]
on anenum
: simple and complex.Simple
enum
seem to be well covered, whereas complexenum
have gaps in functionality or accessible solutions on the web.I cannot understand how to use python pickle on a complex
enum
.Has anyone been able to implement pickling for a complex enum?
Simple Enums can be pickled
When implementing Python pickle for simple enums I generally use the pre-discussed methods of
__getnewargs__
or state setting, preferring__getnewargs__
since it doesn't rely on an external choice of serializer.getnewargs
state setting
Complex Enums can't be pickled as above
State setting
will achieve the error:
getnewargs
gets:
Anyone able shed any light on this? Perhaps I have made a trivial error also...
Beta Was this translation helpful? Give feedback.
All reactions