Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question: How can I pass generic cutom classes to a method #696

Closed
paddywwoof opened this issue Dec 20, 2019 · 2 comments
Closed

Question: How can I pass generic cutom classes to a method #696

paddywwoof opened this issue Dec 20, 2019 · 2 comments

Comments

@paddywwoof
Copy link
Contributor

Background. I am trying to make a python wrapper for a rust version of a 3D graphics module https://github.com/paddywwoof/rust_pi3d see the pyo3_module directory. (Eventually to replace pi3d)
The original python code had

class Shape:
  #lots of methods
  def add_child(self, other):
    self.children.append(other)

class Cuboid(Shape):
  ..
class Sphere(Shape):
  .. etc for lots of different shapes

So all the different shapes are really just wrappers that create a different set of vertices etc of a Shape. This means that they can be used like

cube = Cube()
sphere = Sphere()
cone = Cone()
cube.add_child(sphere)
cube.add_child(cone)

I am struggling to make a generic method like this in pyo3. If you look here there is a test version using #[args(child="*")] . But I'm struggling to check what type I have as an argument.

Is there a much better way to do this?

@kngwyu
Copy link
Member

kngwyu commented Dec 21, 2019

Sorry, I'm not sure I understand the context completely...
But generally, we can't call

fn method<T: Trait>(&self, value: T)  -> ...

from Python.
Now we don't have functionally to pass this kind of functions to Python C-API.
So we need to use one of some tricks.

  1. enum
enum Shapes {
    Cuboid(CuboidImpl),
    Sphere(SphareImpl),
}
#[pyclass] 
struct Shape {
    inner: Shapes,
}
  1. Trait Object
#[pyclass] 
struct Shape {
    inner: Box<dyn ShapeTrait>,
}
  1. Inheritance
#[pyclass] 
struct BaseShape {
...
}

#[pyclass(extends=BaseShape)] 
struct Cuboid {
...
}

@paddywwoof
Copy link
Contributor Author

paddywwoof commented Dec 21, 2019

@kngwyu Thanks so much. I'm pretty sure I don't understand how to apply your options 1. and 2. to this situation, but I can confirm that option 3. Inheritance is exactly what I need. It also gets rid of the massive macro to build all the different shapes and generally simplifies the code. I had got used to rust 'not doing' inheritance in the python way that I didn't even try that.

For the benefit of others this is a minimal example:
in python

import rpi3d
cone = rpi3d.Cone()
cone2 = rpi3d.Cone()
tetra = rpi3d.Tetra()
cone.add_child(tetra)
cone.add_child(cone2)
cone.print_children()

in pyo3 rust

#[pyclass(module="rpi3d")]
struct ShapeBase {
    name: String,
    children: Vec<String>,
}

#[pymethods]
impl ShapeBase {
    fn add_child(&mut self, child: &ShapeBase) {
      self.children.push(child.name.clone());
    }
    fn print_children(&self) {
      println!("{:?}", self.children);
    }
}

#[pyclass(extends=ShapeBase)] 
struct Cone {}
#[pymethods]
impl Cone {
    #[new]
    fn new(obj: &PyRawObject) {
        obj.init({ ShapeBase {
           name: "Cone".to_string(),
           children: vec![],
        } });
    }
}

#[pyclass(extends=ShapeBase)] 
struct Tetra {}
#[pymethods]
impl Tetra {
    #[new]
    fn new(obj: &PyRawObject) {
        obj.init({ ShapeBase {
           name: "Tetra".to_string(),
           children: vec![],
        } });
    }
}
#[pymodule]
fn rpi3d(_py: Python, m: &PyModule) -> PyResult<()> {
    m.add_class::<ShapeBase>()?;
    m.add_class::<Cone>()?;
    m.add_class::<Tetra>()?;
    Ok(())
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants