Reputation: 113
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 usingprint(obj)
, and instead having to writeprint(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.
Upvotes: 11
Views: 1697
Reputation: 43097
This is one possible solution…
The python importing the rust code…
from my_module import my_class
def main():
dsa = my_class(1, True)
print(dsa)
print(dsa.__str__())
if __name__ == "__main__":
main()
The rust source code…
use pyo3::prelude::*;
use pyo3::PyObjectProtocol;
#[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))
}
}
#[pyproto]
impl PyObjectProtocol for my_class {
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(())
}
Upvotes: 0
Reputation: 281958
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.
Upvotes: 12