Phil-ZXX
Phil-ZXX

Reputation: 3245

Correct & concise way to use PyO3 Bindings (similar to pybind11)

Say I have the following C++ Date class. Using pybind11 I can easily wrap it to be used in Python:

/* date.hpp */

class Date
{
public:
    int year, month, day;
    
    Date(int year, int month, int day);

    Date add_days(int N) const;  // Returns new date with N days added
    bool is_weekend() const;     // Returns true if date is Sat or Sun
};
/* date_bindings.cpp */

#include <pybind11/pybind11.h>
#include <date.hpp>

namespace py = pybind11;

PYBIND11_MODULE(date_py, m) {
    py::class_<Date>(m, "Date")
        .def(py::init<int, int, int>(), py::arg("year"), py::arg("month"), py::arg("day"))
        .def("add_days",        &Date::add_days, py::arg("N"))
        .def("is_weekend",      &Date::is_weekend)
        .def_readwrite("year",  &Date::year)
        .def_readwrite("month", &Date::month)
        .def_readwrite("day",   &Date::day);
}

Now, I want to do the same in Rust. So I implemented my Date class and then used PyO3 to wrap it. See code below.

I've noticed that the PyO3 bindings contain a lot of boilerplate, and hence they are much longer compared to pybind11:

Am I using PyO3 correctly? Are there ways to shorten the code to achieve a similar level of "conciseness" as is the case for pybind11?

edit: If there is a way to create bindings without the DatePy helper struct, I would actually prefer that.

/* date.rs */

#[derive(Debug)]
pub struct Date {
    pub year: u16,
    pub month: u8,
    pub day: u8
}

impl Date {
    pub const fn add_days(&self, N: u8) -> Self {
        // Simplified logic for demonstration purposes
        Date{year: self.year, month: self.month, day: self.day + N}
    }
    pub const fn is_weekend(&self) -> bool {
        // ...
        true
    }
}
/* date_bindings.rs */

use pyo3::prelude::*;
use my_library::Date;

#[pyclass(name = "Date")]
struct DatePy {
    _date: Date
}

#[pymethods]
impl DatePy {
    #[new]
    fn new(year: u16, month: u8, day: u8) -> Self {
        DatePy { _date: Date{year, month, day} }
    }
    fn add_days(&self, N: u8) -> PyResult<Self> {
        Ok(DatePy{_date: self._date.add_days(N)})
    }
    fn is_weekend(&self) -> PyResult<bool> {
        Ok(self._date.is_weekend())
    }
    #[getter]
    fn get_year(&self) -> PyResult<u16> {
        Ok(self._date.year)
    }
    #[getter]
    fn get_month(&self) -> PyResult<u8> {
        Ok(self._date.month)
    }
    #[getter]
    fn get_day(&self) -> PyResult<u8> {
        Ok(self._date.day)
    }
    #[setter]
    fn set_year(&mut self, year: u16) -> PyResult<()> {
        self._date.year = year;
        Ok(())
    }
    #[setter]
    fn set_month(&mut self, month: u8) -> PyResult<()> {
        self._date.month = month;
        Ok(())
    }
    #[setter]
    fn set_day(&mut self, day: u8) -> PyResult<()> {
        self._date.day = day;
        Ok(())
    }
}

#[pymodule]
fn date_py(m: &Bound<'_, PyModule>) -> PyResult<()> {
    m.add_class::<DatePy>()?;
    Ok(())
}

Upvotes: 0

Views: 159

Answers (0)

Related Questions