PyO3 / PyO3/pyo3

Dynamic dispatch with Python objects

Open
#877 9 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

needs-design
Dominant language
Rust
Stars
16.2k
Forks
1k
Avg merge
2d 6h
Merged PRs (30d)
66

Description

Hi, I'm trying to pass custom objects through Python to Rust. I need to store one of these in a main struct. The Python classes have some properties of their own and a method I'm calling from the main struct.

This is the Python API I'm shooting for:

main = rust.MainClass()
sub_1 = rust.SubClass01()
sub_2 = rust.SubClass02()

sub_1.a_value = 2
sub_2.a_boolean = True

main.inner_class = sub_1
print(main.do_the_thing())

This is the code I have on the rust side.

#[pyclass]
#[derive(Clone)]
pub struct BaseClass {}

#[pymethods]
impl BaseClass {
    fn do_something(&self) -> f64 {
        -1.0
    }

    #[new]
    fn new() -> Self {
        BaseClass {}
    }

}


#[pyclass(extends=BaseClass)]
#[derive(Clone)]
pub struct SubClass01 {

    #[pyo3(get, set)]
    a_value: u8,
}

#[pymethods]
impl SubClass01 {
    fn do_something(&self) -> f64 {
        1.0
    }

    #[new]
    fn new() -> (Self, BaseClass) {
        (SubClass01 { a_value: 0 }, BaseClass::new())
    }
}


#[pyclass(extends=BaseClass)]
#[derive(Clone)]
pub struct SubClass02 {

    #[pyo3(get, set)]
    a_boolean: bool,
}

#[pymethods]
impl SubClass02 {
    fn do_something(&self) -> f64 {
        if self.a_boolean {
            2.0
        } else {
            0.0
        }
    }

    #[new]
    fn new() -> (Self, BaseClass) {
        (SubClass02 { a_boolean: true }, BaseClass::new())
    }
}


#[pyclass]
pub struct MainClass {

    #[pyo3(get, set)]
    inner_class: BaseClass
}

#[pymethods]
impl MainClass {

    fn do_the_thing(&self) -> f64 {
        self.inner_class.do_something()
    }

    #[new]
    fn new() -> Self {
        MainClass { inner_class: BaseClass {} }
    }
}

The problem is that the subclasses are never set. MainClass always calls BaseClass (the print at the end prints -1.0). I can sort of see why this happens on the rust side since I'm setting BaseClass by default and as the type (I'm guessing the subclasses are getting downcasted?).

I trait objects would be they way to solve this but I don't see how that works in the Pyo3/python side.

Here's what I tried to do with trait objects:


// Yeah this is a terrible trait name :)
pub trait Instanceable {
    fn do_some(&self) {}
}

impl Instanceable for SubClass01 {
    fn do_some(&self) { println!("555"); }
}

impl Default for SubClass01 {
    fn default() -> Self {
        SubClass01 { a_value: 0 }
    }
}

#[pyclass]
pub struct MainClass {
    some_class: Box<dyn Instanceable>
}


#[pymethods]
impl MainClass {

    #[new]
    fn new() -> Self {
        MainClass {
            some_class: Box::new(SubClass01::default())
        }
    }

    #[setter(some_class)]
    fn set_class<T: Instanceable>(&mut self, cls: T) -> PyResult<()> {
        Box::new(cls);
        Ok(())
    }
}

In this case I can't compile because "a python method can't have a generic type parameter", which makes sense.
How do I pass a custom object to the setter? Is this kind of API or dynamic dispatch possible with PyO3?

Thanks!

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

The issue provides no repository file or test entry point. Start by reproducing the Python and Rust examples using PyO3's #[pyclass] inheritance and setter entry points, then determine whether the requested dynamic dispatch is supported and what a completed API should guarantee.

Written by the indexing model from the issue text.

Assessment

Tech stack
python, rust
Domain
api, backend
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
20/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.