Python 函数

(本章代码,如无特殊标注,均在 src/signature.rs 中)

本章内容主要为如何定义一个python可以调用的函数,以及如何处理函数调用时发生的错误

使用 #[pyfunction] 装饰的函数,可以被python调用。

函数签名就是依照编写的rust的函数签名,但是如果想更高自由度的定义函数签名,可以使用 #[pyo3]来进行自由定义

#[pyo3]

函数名指定 name = “…”

// src/lib.rs
#[pyfunction]
#[pyo3(name = "sum_to_string")]
fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
    Ok((a + b).to_string())
}

那么在python调用时只能调用到 sum_to_string 函数,而不能调用到 sum_as_string 函数。

函数签名 signature = ()

绝大部分类型的变量都是直接写在函数签名中即可, 但是如果需要python的None, 则需要使用 Option<T> 来表示,以及要使用 ** 的时候

1. **kwds

#[pyfunction]
#[pyo3(signature = (**kwds))]
fn kwds_args(kwds: Option<&Bound<'_, PyDict>>) -> usize {
    kwds.map_or(0, |dict| dict.len())
}

2. **args

#[pyfunction]
#[pyo3(signature = (*args))]
fn args_args(args: &Bound<'_, PyTuple>) {
    println!("args: {:?}", args);
}

3. None

#[pyfunction]
#[pyo3(signature = (n=None))]
fn none_default(n: Option<i32>) {
    match n {
        Some(n) => println!("n: {}", n),
        None => println!("n is None"),
    }
}

4. 自定义类型

#[pyfunction]
#[pyo3(signature = (r#struct = "foo"))]
fn function_with_keyword(r#struct: &str) {
    /* ... */
}

测试效果

import pyo3_tutorial.signature as signature

if __name__ == "__main__":
    print(signature.kwds_args())
    print(signature.kwds_args(x=1, y=2))
    signature.none_default(None)
    signature.none_default(1)
    signature.args_args(1, 2, 3)

0
2
n is None
n: 1
args: (1, 2, 3)

函数签名 text_signature = (…)

用以修改 __text_signature__ 属性, 在python中调用时显示的函数签名

不是很熟悉这个参数的作用,应该是给编写文档用的,所以应该能够编写更详尽的内容

错误处理

本部分主要为如何在rust部分抛出异常,以及如何在python部分捕获异常

Python Style

// src/math.rs
use pyo3::exceptions::PyZeroDivisionError;

#[pyfunction]
fn divide(a: i32, b: i32) -> PyResult<i32> {
    if b == 0 {
        return Err(PyZeroDivisionError::new_err("b cannot be zero"));
    }
    Ok(a / b)
}

可以看到,我们可以选择抛出python已有的异常,接着使用python代码测试一下效果

if __name__ == "__main__":
    try:
        print(math.divide(1, 0))
    except ZeroDivisionError as e:
        print(e)
    # b cannot be zero

Rust Style

// src/math.rs
use std::num::ParseIntError;

#[pyfunction]
fn parse_int(a: &str) -> Result<i32, ParseIntError> {
    a.parse::<i32>()
}

在这里,我们直接抛出了rust的异常类型,我们来看一下python会怎么处理

if __name__ == "__main__":
    try:
        print(math.parse_int("1aa23"))
    except ValueError as e:
        print(e)
    # invalid digit found in string

可以看到,我们直接catch ValueError就成功了

文档原话为 PyO3 will automatically convert a Result<T, E> returned by a #[pyfunction] into a PyResult<T> as long as there is an implementation of std::from::From<E> for PyErr. Many error types in the Rust standard library have a From conversion defined in this way.

由此可知,我们也可以抛出一些我们新创建的异常类型

TODO