魔术方法和值槽slots
Python的对象模型为不同的对象行为定义了数种协议(protocol),例如 sequence,mapping和 number。在Python中通过魔术方法实现这些协议,例如__str__,__repr__,也被称为双下划线方法(dunder methods)。
在基于 Python C-接口实现PyO3时,许多魔术方法必须放进类对象的值槽(slot)中。如果 #[pymethods] 中的一个函数被识别为魔术方法,它会被自动地放置进Python对象的正确值槽中。
PyO3处理的魔术方法和标准Python非常相像,它们是这里的值槽的子集。一些值槽没有Python中对应的魔术方法,它们需要一些额外的处理:
- 垃圾处理的魔术方法
- 缓存协议的魔术方法
当PyO3处理一个魔术方法时,和其他#[pymethods]有一些区别
- Rust的函数签名必须和魔术方法相匹配
- 不允许
#[pyo3(signature = (...)]和#[pyo3(text_signature = "...")]属性
下面列出所有PyO3处理的魔术方法
- 所有方法接受的第一个参数,显示为
<self>。可以是&self,&mut self或者是形如self_: PyRef<'_, Self>或self_: PyRefMut<'_, Self>的PyCell引用 - 永远允许可选参数
Python<'py>作为第一个参数 - 返回值可以选择被包装进
PyResult object意为任何类型都可以被一个Python对象提取(变量)或者被转换为一个Python对象(返回值)- 其他类型(若有)时必须匹配,例如
pyo3::basic::CompareOp作为__richcmp__的第二个参数 - 对于比较方法(comparison method)或者算数方法(arithmetic method),提取时的错误不表现为一个异常,而是返回一个
NotImplemented - 对一些魔术方法,返回值不受PyO3约束,但是会被Python解释器检查。例如,
__str__需要返回一个字符串对象。这由object(Python type)指明。
基本对象的自定义
__str__(<self>)->object (str)__repr__(<self>)->object (str)__hash__(<self>)->isize
objects that compare equal 必须有相同的哈希值。isize 处可能是返回任意到 64 bits 的类型,PyO3会自动地将其转换为一个 isize,
使Python默认的哈希失效
默认情况下,任何 `#[pyclass]` 类型在Python下有一个默认的哈希实现,不想要被哈希的类型需要替换`__hash__`为`None`。例如#![allow(unused)] fn main() { use pyo3::prelude::*; #[pyclass] struct NotHashable {} #[pymethods] impl NotHashable { #[classattr] const __hash__: Option<PyObject> = None; } }
__richcmp__(<self>, object, pyo3::basic::CompareOp)->object
Overloads Python 的比较算子 (==, !=, <, <=, > 和 >=)。变量 CompareOp 表示这是一个比较算子。
注意到实现 __richcmp__ 会让Python不生成一个默认的 __hash__ 实现, 所以当实现 __richcmp__时考虑实现 __hash__ .
返回类型
返回类型通常是PyResult<bool>,但是其实可以返回任意的Python对象。如果第二个变量object不是在签名中指定的类型,生成的代码会自动返回NotImplemented。
可以使用CompareOp::matches来讲一个Rust的std::cmp::Ordering调整为需要的比较类型。
__getattr__(<self>, object)->object__getattribute__(<self>, object)->object
`__getattr__` 和 `__getattribute__`的区别
在Python中,只有当无法通过常规查找调用属性时会使用__getattr__。而__getattribute__可以通过任意属性方式调用。如果想要获取self中存在的属性,要特别小心不要引入无穷递归,并使用baseclass.__getattribute__()
__setattr__(<self>, value: object)->()__delattr__(<self>, object)->()
覆盖attribute access
__bool__(<self>)->bool
决定对象的真伪值
__call__(<self>, ...)->object- 可以对一般的pymethods定义任意变量列表
可迭代对象
可以用下述方法定义迭代器
__iter__(<self>) -> object__next__(<self>) -> Option<object> or IterNextOutput
在__next__中返回None则表示后面没有元素了。
例子:
#![allow(unused)] fn main() { use pyo3::prelude::*; #[pyclass] struct MyIterator { iter: Box<dyn Iterator<Item = PyObject> + Send>, } #[pymethods] impl MyIterator { fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> { slf } fn __next__(mut slf: PyRefMut<'_, Self>) -> Option<PyObject> { slf.iter.next() } } }
许多时候要区分迭代器和可迭代类型。事实上,可迭代对象只要实现__iter__(),而迭代器必须同时实现__iter__() 和 __next__()。例如:
#![allow(unused)] fn main() { use pyo3::prelude::*; #[pyclass] struct Iter { inner: std::vec::IntoIter<usize>, } #[pymethods] impl Iter { fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> { slf } fn __next__(mut slf: PyRefMut<'_, Self>) -> Option<usize> { slf.inner.next() } } #[pyclass] struct Container { iter: Vec<usize>, } #[pymethods] impl Container { fn __iter__(slf: PyRef<'_, Self>) -> PyResult<Py<Iter>> { let iter = Iter { inner: slf.iter.clone().into_iter(), }; Py::new(slf.py(), iter) } } Python::with_gil(|py| { let container = Container { iter: vec![1, 2, 3, 4] }; let inst = pyo3::PyCell::new(py, container).unwrap(); pyo3::py_run!(py, inst, "assert list(inst) == [1, 2, 3, 4]"); pyo3::py_run!(py, inst, "assert list(iter(iter(inst))) == [1, 2, 3, 4]"); }); }
更多信息见Python官方文档迭代器-类型。
在迭代中返回值
刚才讲了如何使用Option<T>在迭代中yield值。在Python中,生成器也可以返回值。PyO3提供了IterNextOutput枚举来同时Yield值和Return一个最终值。
Awaitable 对象
__await__(<self>)->object__aiter__(<self>)->object__anext__(<self>)->Option<object> or IterANextOutput
Mapping & Sequence类型
这一章中的魔术方法可以用来实现Python的容器(container)类型。Python中有两种主要的容器:"mappings"(例如字典)和 "sequences"(例如列表和元祖)。
PyO3所用的Python C-API 对于 sequence 和 mapping 有不同的值槽。在纯Python中书写时,实现的时候并没有这种区别,例如__getitem__的实现对这两种容器都会填充值槽。
mapping 类型不希望 sequence 值槽被填充,因为这会带来一些不想要结果,例如:
- mapping 类型会成功 cast to
PySequence]. - Python 会对 sequence 提供默认的
__iter__实现,它可以连续地用正整数调用__getitem__直到返回IndexError。
使用#[pyclass(mapping)]标注来指示PyO3只填充 mapping 值槽,保持 sequence值槽为空。这会应用到__getitem__, __setitem__ 和 __delitem__。
使用#[pyclass(sequence)]标注来指示PyO3用sq_length而不是mp_length来填充__len__。这可以帮助例如numpy的库将类识别为一个 sequence。
__len__(<self>)->usize
内置函数len()的实现
__contains__(<self>, object)->bool
Implements membership测试算子。会返回 true 若item 在 self中。对于没有定义__contains__()的对象,该测试对简单地遍历sequence直到找到匹配。
使Python默认的contain失效
默认情况下,所有带有__iter__方法的#[pyclass]类型支持默认的in算子的实现。不想要这个的类型可以将__contains__ 设置为 None。
#![allow(unused)] fn main() { use pyo3::prelude::*; #[pyclass] struct NoContains {} #[pymethods] impl NoContains { #[classattr] const __contains__: Option<PyObject> = None; } }
__getitem__(<self>, object)->object
实现取得self[a]元素
__setitem__(<self>, object, object)->()
实现分配(assign)self[a]元素,仅当元素可以被替换时进行实现
__delitem__(<self>, object)->()
实现删除元素
fn __concat__(&self, other: impl FromPyObject)->PyResult<impl ToPyObject>
通常使用+号,在尝试数值加法__add__ 和 __radd__后拼接两个sequence
fn __repeat__(&self, count: isize)->PyResult<impl ToPyObject>
通常使用*号,在尝试数值乘法__mul__ and __rmul__后重复sequence count次
fn __inplace_concat__(&self, other: impl FromPyObject)->PyResult<impl ToPyObject>
通常使用+=算子,在尝试数值加法__iadd__后相加两个sequence
fn __inplace_repeat__(&self, count: isize)->PyResult<impl ToPyObject>
通常使用*=算子,在尝试数值乘法__imul__后相乘两个sequence
描述符 Descriptors
__get__(<self>, object, object)->object__set__(<self>, object, object)->()__delete__(<self>, object)->()
数值类型
二元数值算子 (+, -, *, @, /, //, %, divmod(),
pow(), **, <<, >>, &, ^ 和 |)
(如果对象 object 不是在签名中指明的类型, 生成的代码会自动return NotImplemented.)
__add__(<self>, object)->object__radd__(<self>, object)->object__sub__(<self>, object)->object__rsub__(<self>, object)->object__mul__(<self>, object)->object__rmul__(<self>, object)->object__matmul__(<self>, object)->object__rmatmul__(<self>, object)->object__floordiv__(<self>, object)->object__rfloordiv__(<self>, object)->object__truediv__(<self>, object)->object__rtruediv__(<self>, object)->object__divmod__(<self>, object)->object__rdivmod__(<self>, object)->object__mod__(<self>, object)->object__rmod__(<self>, object)->object__lshift__(<self>, object)->object__rlshift__(<self>, object)->object__rshift__(<self>, object)->object__rrshift__(<self>, object)->object__and__(<self>, object)->object__rand__(<self>, object)->object__xor__(<self>, object)->object__rxor__(<self>, object)->object__or__(<self>, object)->object__ror__(<self>, object)->object__pow__(<self>, object, object)->object__rpow__(<self>, object, object)->object
占位分配算子(in-place assignment operator):(+=, -=, *=, @=, /=, //=, %=, **=, <<=, >>=, &=, ^=, |=):
__iadd__(<self>, object)->()__isub__(<self>, object)->()__imul__(<self>, object)->()__imatmul__(<self>, object)->()__itruediv__(<self>, object)->()__ifloordiv__(<self>, object)->()__imod__(<self>, object)->()__ipow__(<self>, object, object)->()__ilshift__(<self>, object)->()__irshift__(<self>, object)->()__iand__(<self>, object)->()__ixor__(<self>, object)->()__ior__(<self>, object)->()
一元算子(Unary operations):(-, +, abs() 和 ~):
__pos__(<self>)->object__neg__(<self>)->object__abs__(<self>)->object__invert__(<self>)->object
强制转换?Coercions:
__index__(<self>)->object (int)__int__(<self>)->object (int)__float__(<self>)->object (float)
缓存对象
__getbuffer__(<self>, *mut ffi::Py_buffer, flags)->()__releasebuffer__(<self>, *mut ffi::Py_buffer)->()
__releasebuffer__返回的错误会被传送到sys.unraiseablehook中。强烈建议永远不要从__releasebuffer__返回错误,__releasebuffer__不会被调用第二次,任何没有释放的东西会被泄露。
垃圾收集Garbage Collector Integration
如果类型有对其他Python对象的引用,需要使用Python的垃圾收集器来让GC识别这些引用。这需要实现__traverse__ 和 __clear__,对应于Python C API 中的值槽tp_traverse 和 tp_clear。每次引用另一个Python对象时,__traverse__ 必须调用 visit.call() 。__clear__ 必须清理其他Python对象的任意可变引用(来打破引用cycle)。不可变引用不是必须被清理,因为每个cycle必须包含至少一个可变引用。
__traverse__(<self>, pyo3::class::gc::PyVisit<'_>)->Result<(),pyo3::class::gc::PyTraverseError>__clear__(<self>)->()
例子
#![allow(unused)] fn main() { use pyo3::prelude::*; use pyo3::PyTraverseError; use pyo3::gc::PyVisit; #[pyclass] struct ClassWithGCSupport { obj: Option<PyObject>, } #[pymethods] impl ClassWithGCSupport { fn __traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError> { if let Some(obj) = &self.obj { visit.call(obj)? } Ok(()) } fn __clear__(&mut self) { // Clear reference, this decrements ref counter. self.obj = None; } } }