#[args(…)]
方法参数。对于某些余姚default value的值进行定义,如果重新赋值会覆盖掉default-value
这里有个问题就是赋值只接受字符串格式,对于原来非字符串格式直接加“ 但是原来字符串格式的变换略微不同,详见如下例子:
#[args(years="10")]
pub fn add_year(&mut self, years:i32) ->PyResult<i32>{
let k = self.year + years;
println!("now I'm {} years old", k);
Ok(k)
}
import rust_give_python as ru
if __name__ == '__main__':
a = ru.test_class()
print(a.add_year())
now I'm 90 years old
#[args(some_string="\"None\"")]
pub fn test_fuc(&mut self , some_string:&str){
println!("{}", some_string)
}
import rust_give_python as ru
if __name__ == '__main__':
a = ru.test_class()
print(a.test_fuc("Hello"))
print(a.test_fuc())
Hello
None
魔术方法
在Rust定义类的时候免不了要考虑魔术方法, 在PyO3中倒是给我们做了很方便的封装:只要如果方法的名称是公认的魔法方法,PyO3 会自动将其放入类型对象中。(原来的另一种方法被废弃了,[自 PyO3 0.16 起已弃用] 在与属性结合的特殊特征中#[pyproto]
。)
Rust中可定义的魔术方法: str, repr(获取对象内存地址), hash, richcmp, getattribute, getattr, setattr, delattr, bool,call. 以及iter和next(没想到竟然支持迭代器)
魔术方法确实有点多,看官网文档把, 同时有大佬整理的图片,https://www.cnblogs.com/traditional/p/13611233.html, 这个人感觉还是挺厉害的
// 一个简单斐波那契数列的生成器例子
#[pyclass]
pub struct iter_f{
small_num:i32,
big_num:i32,
max_num:i32,
}
#[pymethods]
impl iter_f {
#[new]
fn new() -> iter_f{
iter_f{small_num:0,big_num:1, max_num:10}
}
fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
slf
}
}
#[pyproto]
impl PyIterProtocol for iter_f{
fn __next__(mut slf: PyRefMut<Self>) ->IterNextOutput<i32, &'static str>{
if slf.max_num > 0 {
let a = slf.small_num;
slf.small_num = slf.big_num;
slf.big_num += a;
slf.max_num -= 1;
return IterNextOutput::Yield(slf.small_num);
}
else{
return IterNextOutput::Return("Ended");
}
}
}
import rust_give_python as ru
if __name__ == '__main__':
k = ru.iter_f()
for p in k:
print(p)
1 2 3 5 8 13 21 34 55
必要的时候我们要为自己的类实现GC保证回收:实现这两个方法__traverse__
和__clear__
。__traverse__
必须调用visit.call()
对另一个 Python 对象的每个引用。 __clear__
必须清除对其他 Python 对象的任何可变引用(从而打破引用循环)。不可变引用不必被清除,因为每个循环必须至少包含一个可变引用。(上面这一段比较恶心,我们直接看例子):
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;
}
}
_call_ 装饰器
其实讲道理_call__也是魔术方法应该放在上一节,不过那样就有点太巨无霸了,我不喜欢,所以在这里放了__call_ .使用call的类可以包装为修饰器,不过这个装饰器的包裹有点意思我研究一下:
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyTuple};
/// A function decorator that keeps track how often it is called.
///
/// It otherwise doesn't do anything special.
#[pyclass(name = "Counter")]
pub struct PyCounter {
// We use `#[pyo3(get)]` so that python can read the count but not mutate it.
#[pyo3(get)]
count: u64,
// This is the actual function being wrapped.
wraps: Py<PyAny>,
}
#[pymethods]
impl PyCounter {
// Note that we don't validate whether `wraps` is actually callable.
//
// While we could use `PyAny::is_callable` for that, it has some flaws:
// 1. It doesn't guarantee the object can actually be called successfully
// 2. We still need to handle any exceptions that the function might raise
#[new]
fn __new__(wraps: Py<PyAny>) -> Self {
PyCounter { count: 0, wraps }
}
#[args(args = "*", kwargs = "**")]
fn __call__(
&mut self,
py: Python<'_>,
args: &PyTuple,
kwargs: Option<&PyDict>,
) -> PyResult<Py<PyAny>> {
self.count += 1;
let name = self.wraps.getattr(py, "__name__")?;
println!("{} has been called {} time(s).", name, self.count);
// After doing something, we finally forward the call to the wrapped function
let ret = self.wraps.call(py, args, kwargs)?;
// We could do something with the return value of
// the function before returning it
Ok(ret)
}
}
#[pymodule]
pub fn decorator(_py: Python<'_>, module: &PyModule) -> PyResult<()> {
module.add_class::<PyCounter>()?;
Ok(())
}
等价于:
class Counter:
def __init__(self, wraps):
self.count = 0
self.wraps = wraps
def __call__(self, *args, **kwargs):
self.count += 1
print(f"{self.wraps.__name__} has been called {self.count} time(s)")
self.wraps(*args, **kwargs)
# 或等价于
def Counter(wraps):
count = 0
def call(*args, **kwargs):
nonlocal count
count += 1
print(f"{wraps.__name__} has been called {count} time(s)")
return wraps(*args, **kwargs)
return call
我尝试利用wrap包裹函数完成多线程运行,然后(GIL罪大恶极):
#[pyfunction]
pub fn test_thread(wraps: Py<PyAny>, py: Python<'_>) {
let m = wraps.clone();
unsafe {
// 我专门套了一层Rust让这种线程编译能过,然后
let a = thread::spawn(move || {
m.call0(py).unwrap()
});
let b = thread::spawn(move || { wraps.call0(py).unwrap() });
a.join();
b.join();
}
}
错误原因如下:因为GIL的缘故这个Py<PyAny> 不能传递,不能多线程,真的麻中麻
error[E0277]: *mut pyo3::Python<'static>
cannot be sent between threads safely
–> src\lib.rs:164:13
|
164 | let a = thread::spawn(move || {
| ____________^^^^^^^^^^^^^-
| | |
| | *mut pyo3::Python<'static>
cannot be sent between threads safely
165 | | m.call0(py).unwrap()
166 | | });
| |_____- within this [closure@src\lib.rs:164:27: 166:6]
继承
我们在PyO3中定义的类不允许继承,哪怕使用type这样的函数也无法继承父类:
from rust_give_python import *
k = type('h',(test_class,),{})
TypeError: type 'builtins.test_class' is not an acceptable base type
我翻了翻资料,猜测的理由:没有__class__方法。正常在Python中定义的对象都有__class__方法,而我们得到的type一般是 <class ‘__main__.*’>。很遗憾,虽然Rust拓展了很多魔法方法,但是并没有__ class__方法,这也是让人头疼的点之一。此外,继承之后这个类去调用继承的方法的时候应该也会用到__dict__查看方法名,这个东西也没有被实现,,
后来在github上发现了解决方法,阿哲,我是sb:
#[pyclass(subclass)]
pub struct test_class{
......
}
if __name__ == 'main':
k = type('h',(test_class,),{})
print(test_class)
print(k)
<class 'builtins.test_class'>
<class '__main__.h'>
#继承了__str__方法
test_class:{
year:80
id:1
name:hello
}
# 这也同时说明,有没有dict跟继承毫无关系
{}