Classes

We've covered Python functions written in Rust, but what about classes?

Defining a class

You can use the #[pyclass] attribute to define a new Python class in Rust. Here's an example:

#![allow(unused)]
fn main() {
use pyo3::prelude::*;

#[pyclass]
struct Wallet {
    balance: i32,
}
}

It defines a new Python class called Wallet with a single field, balance.

Registering a class

Just like with #[pyfunction]s, you must explicitly register your class with a module to make it visible to users of your extension.
Continuing with the example above, you'd register the Wallet class like this:

#![allow(unused)]
fn main() {
#[pymodule]
fn my_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
    m.add_class::<Wallet>()?;
    Ok(())
}
}

IntoPy

Rust types that have been annotated with #[pyclass] automatically implement the IntoPy trait, thus allowing you to return them from your #[pyfunction]s.

For example, you can define a function that creates a new Wallet instance:

#![allow(unused)]
fn main() {
#[pyfunction]
fn new_wallet(balance: i32) -> Wallet {
    Wallet { balance }
}
}

It'll compile just fine, handing over a new Wallet instance to the Python caller.

Attributes

By default, the fields of your #[pyclass]-annotated structs aren't accessible to Python callers.
Going back to our Wallet example—if you try to access the balance field from Python, you'll get an error:

        wallet = new_wallet(0)
>       assert wallet.balance == 0
E       AttributeError: 'builtins.Wallet' object has no attribute 'balance'

tests/test_sample.py:8: AttributeError

The same error would occur even if you made balance a public field.

To make the field accessible to Python, you must add a getter.
This can be done using the #[pyo3(get)] attribute:

#![allow(unused)]
fn main() {
#[pyclass]
struct Wallet {
    #[pyo3(get)]
    balance: i32,
}
}

Now, the balance field is accessible from Python:

def test_wallet():
    wallet = new_wallet(0)
    assert wallet.balance == 0

If you want to allow Python callers to modify the field, you can add a setter using the #[pyo3(set)] attribute:

#![allow(unused)]
fn main() {
#[pyclass]
struct Wallet {
    // Both getter and setter
    #[pyo3(get, set)]
    balance: i32,
}
}

Exercise

The exercise for this section is located in 02_classes/00_pyclass