Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

__str__ function of class ported from rust to python using pyo3 doesn't get used in print

I am using the pyo3 rust crate (version 0.11.1) in order to port rust code, into cpython (version 3.8.2) code. I have created a class called my_class and defined the following functions: new, __str__, and __repr__.

TL;DR: The __str__ function exists on a class ported from rust using the pyo3 crate, but doesn't get printed when just using print(obj), and instead having to write print(obj.__str__())

The my_class definition is here:

use pyo3::prelude::*;

#[pyclass]
struct my_class {
    #[pyo3(get, set)]
    num: i32,
    #[pyo3(get, set)]
    debug: bool,
}

#[pymethods]
impl my_class {
    #[new]
    fn new(num: i32, debug: bool) -> Self {
        my_class {num, debug}
    }
    fn __str__(&self) -> PyResult<String>   {
        Ok(format!("[__str__] Num: {}, Debug: {}", self.num, self.debug))
    }

    fn __repr__(&self) -> PyResult<String> {
        Ok(format!("[__repr__] Num: {}, Debug: {}", self.num, self.debug))
    }
}

#[pymodule]
fn pymspdb(_py: Python, m: &PyModule) -> PyResult<()> {
    m.add_class::<my_class>()?;
    Ok(())
}

I build this (into release mode), and test the code with the following code:

from my_module import my_class

def main():
    dsa = my_class(1, True)
    print(dsa)
    print(dsa.__str__())
    
    
if __name__ == "__main__":
    main()

When running the test python code, I get the following output:

<my_class object at 0x7fb7828ae950>
[__str__] Num: 1, Debug: true

Now I have thought of possible solutions to this. One solution might be the pyo3 rust crate actually acts as a proxy, and in order to port classes into python might implement some sort of object which transfers all actions over to the ported class. So it might not implement its own __str__ therefore not giving me what I want. The second possible solution I thought of was I might not be overloading the __str__ function properly, therefore when python tries to use the print function it doesn't access the correct function and just does the default behavior.

Thanks for reading so far, hope I can find an answer since I didn't find anything online for this.

like image 410
fdsa Avatar asked Jun 30 '20 22:06

fdsa


1 Answers

I'm pretty sure this is because you need to implement these methods through the PyObjectProtocol trait.

Many Python __magic__ methods correspond to C-level function pointer slots in a type object's memory layout. A type implemented in C needs to provide a function pointer in the slot, and Python will automatically generate a method to wrap the pointer for explicit method calls. A type implemented in Python will automatically have function pointers inserted that delegate to the magic methods.

The Python internals will usually look for the function pointer rather than the corresponding magic method, and if Python doesn't find the function pointer, it will behave as though the method doesn't exist. That's why, for example, you had to use #[new] to mark your constructor instead of implementing a __new__ static method.

__str__ and __repr__ also correspond to function pointers - specifically, tp_str and tp_repr. If you just try to implement them as regular methods, pyo3 won't generate the function pointers needed. PyObjectProtocol is the pyo3 interface to go through for that.

like image 139
user2357112 supports Monica Avatar answered Oct 20 '22 10:10

user2357112 supports Monica