Merge pull request #3243 from PyO3/classes-and-free-functions

Extend guide section on classes regarding free functions
This commit is contained in:
Adam Reichold 2023-06-16 11:10:01 +00:00 committed by GitHub
commit 5b85c924dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 129 additions and 45 deletions

View File

@ -17,6 +17,7 @@ This chapter will discuss the functionality and configuration these attributes o
- [`#[classattr]`](#class-attributes)
- [`#[args]`](#method-arguments)
- [Magic methods and slots](class/protocols.html)
- [Classes as function arguments](#classes-as-function-arguments)
## Defining a new class
@ -226,8 +227,10 @@ struct FrozenCounter {
value: AtomicUsize,
}
let py_counter: Py<FrozenCounter> = Python::with_gil(|py| {
let counter = FrozenCounter { value: AtomicUsize::new(0) };
let py_counter: Py<FrozenCounter> = Python::with_gil(|py| {
let counter = FrozenCounter {
value: AtomicUsize::new(0),
};
Py::new(py, counter).unwrap()
});
@ -647,9 +650,9 @@ impl BaseClass {
#[new]
#[classmethod]
fn py_new<'p>(cls: &'p PyType, py: Python<'p>) -> PyResult<Self> {
// Get an abstract attribute (presumably) declared on a subclass of this class.
let subclass_attr = cls.getattr("a_class_attr")?;
Ok(Self(subclass_attr.to_object(py)))
// Get an abstract attribute (presumably) declared on a subclass of this class.
let subclass_attr = cls.getattr("a_class_attr")?;
Ok(Self(subclass_attr.to_object(py)))
}
}
```
@ -716,6 +719,67 @@ impl MyClass {
}
```
## Classes as function arguments
Free functions defined using `#[pyfunction]` interact with classes through the same mechanisms as the self parameters of instance methods, i.e. they can take GIL-bound references, GIL-bound reference wrappers or GIL-indepedent references:
```rust
# #![allow(dead_code)]
# use pyo3::prelude::*;
#[pyclass]
struct MyClass {
my_field: i32,
}
// Take a GIL-bound reference when the underlying `PyCell` is irrelevant.
#[pyfunction]
fn increment_field(my_class: &mut MyClass) {
my_class.my_field += 1;
}
// Take a GIL-bound reference wrapper when borrowing should be automatic,
// but interaction with the underlying `PyCell` is desired.
#[pyfunction]
fn print_field(my_class: PyRef<'_, MyClass>) {
println!("{}", my_class.my_field);
}
// Take a GIL-bound reference to the underlying cell
// when borrowing needs to be managed manaually.
#[pyfunction]
fn increment_then_print_field(my_class: &PyCell<MyClass>) {
my_class.borrow_mut().my_field += 1;
println!("{}", my_class.borrow().my_field);
}
// Take a GIL-indepedent reference when you want to store the reference elsewhere.
#[pyfunction]
fn print_refcnt(my_class: Py<MyClass>, py: Python<'_>) {
println!("{}", my_class.get_refcnt(py));
}
```
Classes can also be passed by value if they can be be cloned, i.e. they automatically implement `FromPyObject` if they implement `Clone`, e.g. via `#[derive(Clone)]`:
```rust
# #![allow(dead_code)]
# use pyo3::prelude::*;
#[pyclass]
#[derive(Clone)]
struct MyClass {
my_field: Box<i32>,
}
#[pyfunction]
fn dissamble_clone(my_class: MyClass) {
let MyClass { mut my_field } = my_class;
*my_field += 1;
}
```
Note that `#[derive(FromPyObject)]` on a class is usually not useful as it tries to construct a new Rust value by filling in the fields by looking up attributes of any given Python value.
## Method arguments
Similar to `#[pyfunction]`, the `#[pyo3(signature = (...))]` attribute can be used to specify the way that `#[pymethods]` accept arguments. Consult the documentation for [`function signatures`](./function/signature.md) to see the parameters this attribute accepts.

View File

@ -72,7 +72,12 @@ A [previous implementation] used a normal `u64`, which meant it required a `&mut
```rust,ignore
#[pyo3(signature = (*args, **kwargs))]
fn __call__(&mut self, py: Python<'_>, args: &PyTuple, kwargs: Option<&PyDict>) -> PyResult<Py<PyAny>> {
fn __call__(
&mut self,
py: Python<'_>,
args: &PyTuple,
kwargs: Option<&PyDict>,
) -> PyResult<Py<PyAny>> {
self.count += 1;
let name = self.wraps.getattr(py, "__name__")?;

View File

@ -266,16 +266,18 @@ use pyo3::prelude::*;
#[derive(FromPyObject)]
# #[derive(Debug)]
enum RustyEnum<'a> {
Int(usize), // input is a positive int
String(String), // input is a string
IntTuple(usize, usize), // input is a 2-tuple with positive ints
Int(usize), // input is a positive int
String(String), // input is a string
IntTuple(usize, usize), // input is a 2-tuple with positive ints
StringIntTuple(String, usize), // input is a 2-tuple with String and int
Coordinates3d { // needs to be in front of 2d
Coordinates3d {
// needs to be in front of 2d
x: usize,
y: usize,
z: usize,
},
Coordinates2d { // only gets checked if the input did not have `z`
Coordinates2d {
// only gets checked if the input did not have `z`
#[pyo3(attribute("x"))]
a: usize,
#[pyo3(attribute("y"))]

View File

@ -115,9 +115,7 @@ The `#[pyo3]` attribute can be used on individual arguments to modify properties
}
#[pyfunction]
fn object_length(
#[pyo3(from_py_with = "get_length")] argument: usize
) -> usize {
fn object_length(#[pyo3(from_py_with = "get_length")] argument: usize) -> usize {
argument
}

View File

@ -85,10 +85,7 @@ Arguments of type `Python` must not be part of the signature:
# use pyo3::prelude::*;
#[pyfunction]
#[pyo3(signature = (lambda))]
pub fn simple_python_bound_function(
py: Python<'_>,
lambda: PyObject,
) -> PyResult<()> {
pub fn simple_python_bound_function(py: Python<'_>, lambda: PyObject) -> PyResult<()> {
Ok(())
}
```

View File

@ -196,9 +196,8 @@ We can avoid the delay in releasing memory if we are careful to drop the
# use pyo3::prelude::*;
# use pyo3::types::PyString;
# fn main() -> PyResult<()> {
let hello: Py<PyString> = Python::with_gil(|py| {
py.eval("\"Hello World!\"", None, None)?.extract()
})?;
let hello: Py<PyString> =
Python::with_gil(|py| py.eval("\"Hello World!\"", None, None)?.extract())?;
// Do some stuff...
// Now sometime later in the program:
Python::with_gil(|py| {
@ -219,9 +218,8 @@ until the GIL is dropped.
# use pyo3::prelude::*;
# use pyo3::types::PyString;
# fn main() -> PyResult<()> {
let hello: Py<PyString> = Python::with_gil(|py| {
py.eval("\"Hello World!\"", None, None)?.extract()
})?;
let hello: Py<PyString> =
Python::with_gil(|py| py.eval("\"Hello World!\"", None, None)?.extract())?;
// Do some stuff...
// Now sometime later in the program:
Python::with_gil(|py| {

View File

@ -29,7 +29,7 @@ After:
# use pyo3::prelude::*;
#[pyfunction]
#[pyo3(signature = (x, y))] // both x and y have no defaults and are required
#[pyo3(signature = (x, y))] // both x and y have no defaults and are required
fn x_or_y(x: Option<u64>, y: u64) -> u64 {
x.unwrap_or(y)
}
@ -103,12 +103,16 @@ fn raise_err() -> anyhow::Result<()> {
fn main() {
Python::with_gil(|py| {
let rs_func = wrap_pyfunction!(raise_err, py).unwrap();
pyo3::py_run!(py, rs_func, r"
pyo3::py_run!(
py,
rs_func,
r"
try:
rs_func()
except Exception as e:
print(repr(e))
");
"
);
})
}
# }
@ -182,9 +186,7 @@ drop(second);
// Or it ensure releasing the inner lock before the outer one.
Python::with_gil(|py| {
let first = Object::new(py);
let second = Python::with_gil(|py| {
Object::new(py)
});
let second = Python::with_gil(|py| Object::new(py));
drop(first);
drop(second);
});
@ -207,7 +209,7 @@ Before, x in the below example would be required to be passed from Python code:
# use pyo3::prelude::*;
#[pyfunction]
fn required_argument_after_option(x: Option<i32>, y: i32) { }
fn required_argument_after_option(x: Option<i32>, y: i32) {}
```
After, specify the intended Python signature explicitly:
@ -218,11 +220,11 @@ After, specify the intended Python signature explicitly:
// If x really was intended to be required
#[pyfunction(signature = (x, y))]
fn required_argument_after_option_a(x: Option<i32>, y: i32) { }
fn required_argument_after_option_a(x: Option<i32>, y: i32) {}
// If x was intended to be optional, y needs a default too
#[pyfunction(signature = (x=None, y=0))]
fn required_argument_after_option_b(x: Option<i32>, y: i32) { }
fn required_argument_after_option_b(x: Option<i32>, y: i32) {}
```
### `__text_signature__` is now automatically generated for `#[pyfunction]` and `#[pymethods]`

View File

@ -218,18 +218,26 @@ can be used to generate a Python module which can then be used just as if it was
to this function!
```rust
use pyo3::{prelude::*, types::{IntoPyDict, PyModule}};
use pyo3::{
prelude::*,
types::{IntoPyDict, PyModule},
};
# fn main() -> PyResult<()> {
Python::with_gil(|py| {
let activators = PyModule::from_code(py, r#"
let activators = PyModule::from_code(
py,
r#"
def relu(x):
"""see https://en.wikipedia.org/wiki/Rectifier_(neural_networks)"""
return max(0.0, x)
def leaky_relu(x, slope=0.01):
return x if x >= 0 else x * slope
"#, "activators.py", "activators")?;
"#,
"activators.py",
"activators",
)?;
let relu_result: f64 = activators.getattr("relu")?.call1((-1.0,))?.extract()?;
assert_eq!(relu_result, 0.0);
@ -359,7 +367,10 @@ The example below shows:
use pyo3::prelude::*;
fn main() -> PyResult<()> {
let py_foo = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/python_app/utils/foo.py"));
let py_foo = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/python_app/utils/foo.py"
));
let py_app = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/python_app/app.py"));
let from_python = Python::with_gil(|py| -> PyResult<Py<PyAny>> {
PyModule::from_code(py, py_foo, "utils.foo", "utils.foo")?;
@ -422,7 +433,9 @@ use pyo3::types::PyModule;
fn main() {
Python::with_gil(|py| {
let custom_manager = PyModule::from_code(py, r#"
let custom_manager = PyModule::from_code(
py,
r#"
class House(object):
def __init__(self, address):
self.address = address
@ -434,7 +447,11 @@ class House(object):
else:
print(f"Thank you for visiting {self.address}, come again soon!")
"#, "house.py", "house").unwrap();
"#,
"house.py",
"house",
)
.unwrap();
let house_class = custom_manager.getattr("House").unwrap();
let house = house_class.call1(("123 Main Street",)).unwrap();
@ -448,13 +465,14 @@ class House(object):
match result {
Ok(_) => {
let none = py.None();
house.call_method1("__exit__", (&none, &none, &none)).unwrap();
},
house
.call_method1("__exit__", (&none, &none, &none))
.unwrap();
}
Err(e) => {
house.call_method1(
"__exit__",
(e.get_type(py), e.value(py), e.traceback(py))
).unwrap();
house
.call_method1("__exit__", (e.get_type(py), e.value(py), e.traceback(py)))
.unwrap();
}
}
})