Methods

The #[pymethods] attribute is not limited to constructors. You can use it to attach any number of methods to your #[pyclass]:

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

#[pyclass]
struct Wallet {
    #[pyo3(get, set)]
    balance: i32,
}

#[pymethods]
impl Wallet {
    #[new]
    fn new(balance: i32) -> Self {
        Wallet { balance }
    }

    fn deposit(&mut self, amount: i32) {
        self.balance += amount;
    }

    fn withdraw(&mut self, amount: i32) {
        self.balance -= amount;
    }
}
}

All methods within an impl block annotated with #[pymethods] are automatically exposed to Python as methods on your #[pyclass]1. The deposit and withdraw methods in the example above can be called from Python like this:

wallet = Wallet(0)
wallet.deposit(100)
wallet.withdraw(50)
assert wallet.balance == 50

multiple-pymethods

You can't annotate multiple impl blocks with #[pymethods] for the same class, due to a limitation in Rust's metaprogramming capabilities.
There is a way to work around this issue using some linker dark magic, via the multiple-pymethods feature flag, but it comes with a penalty in terms of compile times as well as limited cross-platform support. Check out pyo3's documentation for more details.

Footnotes

1

All methods in a #[pymethods] block are exposed, even if they are private!

Exercise

The exercise for this section is located in 02_classes/02_methods