自定义基本对象
回忆上一章中的Number
类
#![allow(unused)] fn main() { use pyo3::prelude::*; #[pyclass] struct Number(i32); #[pymethods] impl Number { #[new] fn new(value: i32) -> Self { Self(value) } } #[pymodule] fn my_module(_py: Python<'_>, m: &PyModule) -> PyResult<()> { m.add_class::<Number>()?; Ok(()) } }
目前Python可以导入这个模组,获取到这个类,并创建实例——没别的了。
from my_module import Number
n = Number(5)
print(n)
<builtins.Number object at 0x000002B4D185D7D0>
字符串表示String representations
它甚至不能打印一个用户可见的它自身的说明!我们可以在#[pymethods]
块中加上__repr__
和 __str__
方法,用这个来获得Number
中包含的值。methods inside a
#![allow(unused)] fn main() { use pyo3::prelude::*; #[pyclass] struct Number(i32); #[pymethods] impl Number { // For `__repr__` we want to return a string that Python code could use to recreate // the `Number`, like `Number(5)` for example. fn __repr__(&self) -> String { // We use the `format!` macro to create a string. Its first argument is a // format string, followed by any number of parameters which replace the // `{}`'s in the format string. // // 👇 Tuple field access in Rust uses a dot format!("Number({})", self.0) } // `__str__` is generally used to create an "informal" representation, so we // just forward to `i32`'s `ToString` trait implementation to print a bare number. fn __str__(&self) -> String { self.0.to_string() } } }
哈希Hashing
接下来实现哈希。我们仅仅哈希i32
,为此我们需要一个Hash
,std
提供的是DefaultHasher
,使用的是SipHash算法。
#![allow(unused)] fn main() { use std::collections::hash_map::DefaultHasher; // Required to call the `.hash` and `.finish` methods, which are defined on traits. use std::hash::{Hash, Hasher}; use pyo3::prelude::*; #[pyclass] struct Number(i32); #[pymethods] impl Number { fn __hash__(&self) -> u64 { let mut hasher = DefaultHasher::new(); self.0.hash(&mut hasher); hasher.finish() } } }
Note: 当实现
__hash__
和比较时, 下属性质需要满足k1 == k2 -> hash(k1) == hash(k2)
也就是说两个键值相等时,它们的哈希也要相等。 另外必须注意类的哈希不能在生命周期中改变。在这里的教程中,我们不让Python代码改变我们的
Number
类,也就是说,它是不可变的。 默认情况下,所有#[pyclass]
类型在Python中有一个默认的哈希实现。 不需要被哈希的类型需要改写__hash__
为None
#![allow(unused)] fn main() { use pyo3::prelude::*; #[pyclass] struct NotHashable {} #[pymethods] impl NotHashable { #[classattr] const __hash__: Option<Py<PyAny>> = None; } }
比较Comparisons
不像在Python中,PyO3不提供类似__eq__
, __lt__
的魔术比较方法。你需要通过__richcmp__
一次性实现所有六个算子。
Unlike in Python, PyO3 does not provide the magic comparison methods you might expect like __eq__
, 这个方法会根据算子的CompareOp
值被调用
#![allow(unused)] fn main() { use pyo3::class::basic::CompareOp; use pyo3::prelude::*; #[pyclass] struct Number(i32); #[pymethods] impl Number { fn __richcmp__(&self, other: &Self, op: CompareOp) -> PyResult<bool> { match op { CompareOp::Lt => Ok(self.0 < other.0), CompareOp::Le => Ok(self.0 <= other.0), CompareOp::Eq => Ok(self.0 == other.0), CompareOp::Ne => Ok(self.0 != other.0), CompareOp::Gt => Ok(self.0 > other.0), CompareOp::Ge => Ok(self.0 >= other.0), } } } }
如果通过比较两个Rust值获得了结果,可以采取一个捷径CompareOp::matches
#![allow(unused)] fn main() { use pyo3::class::basic::CompareOp; use pyo3::prelude::*; #[pyclass] struct Number(i32); #[pymethods] impl Number { fn __richcmp__(&self, other: &Self, op: CompareOp) -> bool { op.matches(self.0.cmp(&other.0)) } } }
它会检查Rust的 Ord
中得到的 std::cmp::Ordering
是否匹配给定的CompareOp
。
另外,如果想要保留一些算子不被实现,可以返回py.NotImplemented()
#![allow(unused)] fn main() { use pyo3::class::basic::CompareOp; use pyo3::prelude::*; #[pyclass] struct Number(i32); #[pymethods] impl Number { fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> PyObject { match op { CompareOp::Eq => (self.0 == other.0).into_py(py), CompareOp::Ne => (self.0 != other.0).into_py(py), _ => py.NotImplemented(), } } } }
真伪Truthyness
考虑Number
是True
如果它不是零
#![allow(unused)] fn main() { use pyo3::prelude::*; #[pyclass] struct Number(i32); #[pymethods] impl Number { fn __bool__(&self) -> bool { self.0 != 0 } } }
最终的代码
#![allow(unused)] fn main() { use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; use pyo3::prelude::*; use pyo3::class::basic::CompareOp; #[pyclass] struct Number(i32); #[pymethods] impl Number { #[new] fn new(value: i32) -> Self { Self(value) } fn __repr__(&self) -> String { format!("Number({})", self.0) } fn __str__(&self) -> String { self.0.to_string() } fn __hash__(&self) -> u64 { let mut hasher = DefaultHasher::new(); self.0.hash(&mut hasher); hasher.finish() } fn __richcmp__(&self, other: &Self, op: CompareOp) -> PyResult<bool> { match op { CompareOp::Lt => Ok(self.0 < other.0), CompareOp::Le => Ok(self.0 <= other.0), CompareOp::Eq => Ok(self.0 == other.0), CompareOp::Ne => Ok(self.0 != other.0), CompareOp::Gt => Ok(self.0 > other.0), CompareOp::Ge => Ok(self.0 >= other.0), } } fn __bool__(&self) -> bool { self.0 != 0 } } #[pymodule] fn my_module(_py: Python<'_>, m: &PyModule) -> PyResult<()> { m.add_class::<Number>()?; Ok(()) } }