Merge pull request #3243 from PyO3/classes-and-free-functions
Extend guide section on classes regarding free functions
This commit is contained in:
commit
5b85c924dd
|
@ -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.
|
||||
|
|
|
@ -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__")?;
|
||||
|
||||
|
|
|
@ -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"))]
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
```
|
||||
|
|
|
@ -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| {
|
||||
|
|
|
@ -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]`
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue