Custom setters and getters
In a previous section, we learned how to attach the default getter and setter to a field in a #[pyclass]:
#![allow(unused)]
fn main() {
use pyo3::prelude::*;
#[pyclass]
struct Wallet {
#[pyo3(get, set)]
balance: i32,
}
}
This is convenient, but it’s not always desirable!
Let’s introduce an additional constraint to our Wallet struct: the balance should never go below a pre-determined
overdraft threshold.
We’d start by enforcing this constraint in the constructor method:
#![allow(unused)]
fn main() {
use pyo3::prelude::*;
use pyo3::exceptions::PyValueError;
#[pyclass]
struct Wallet {
#[pyo3(get, set)]
balance: i32,
}
const OVERDRAFT_LIMIT: i32 = -100;
#[pymethods]
impl Wallet {
#[new]
fn new(balance: i32) -> PyResult<Self> {
if balance < OVERDRAFT_LIMIT {
return Err(PyValueError::new_err("Balance cannot be below overdraft limit"));
}
Ok(Wallet { balance })
}
}
}
Wallet::new ensures that a newly-created Wallet upholds the overdraft constraint. But the default setter
can be easily used to circumvent the limit:
wallet = Wallet(0)
wallet.balance = -200 # This should not be allowed, but it is!
#[setter] and #[getter]
We can override the default getter and setter by defining custom methods for them.
Here’s how we can implement a custom setter for the balance field via the #[setter] attribute:
#![allow(unused)]
fn main() {
use pyo3::prelude::*;
#[pyclass]
struct Wallet {
// We keep using the default getter, no issues there
#[pyo3(get)]
balance: i32,
}
const OVERDRAFT_LIMIT: i32 = -100;
#[pymethods]
impl Wallet {
#[new]
fn new(balance: i32) -> PyResult<Self> {
Wallet::check_balance(balance)?;
Ok(Wallet { balance })
}
#[setter]
fn set_balance(&mut self, value: i32) {
Wallet::check_balance(value)?;
self.balance = value;
}
}
impl Wallet {
// We put this method in a separate `impl` block to avoid exposing it to Python
fn check_balance(balance: i32) -> PyResult<()> {
if balance < OVERDRAFT_LIMIT {
return Err(PyValueError::new_err("Balance cannot be below overdraft limit"));
}
Ok(())
}
}
}
Every time the balance field is set in Python, Wallet::set_balance will be called:
wallet = Wallet(0)
wallet.balance = -200 # Now raises a `ValueError`
The field is associated with its setter using a conventional naming strategy for the setter method: set_<field_name>.
You can also explicitly specify the field name in the #[setter] attribute, like this: #[setter(balance)].
Custom getters are defined in a similar way using the #[getter] attribute. The naming convention for
getter methods is <field_name>, but you can also specify the field name explicitly in the attribute—e.g.
#[getter(balance)].
Exercise
The exercise for this section is located in 02_classes/03_setters