Search code examples
pythonrustpyo3

Return a Python object defined in a third party Python module (e.g. Numpy) using PyO3


Motivation

This question is framed in terms of a specific example, for concreteness, but it's part of a much bigger question of how to return objects in pyo3, generally.

Problem

I'm writing a python module with a Rust backend using PyO3.

I'd like to write a wrapper for a function that returns a rational number (of type Ratio<isize>); the wrapped function should return a Python Fraction. I'm not sure how to do this, but it would probably suffice to be able to reproduce the following function with PyO3 would be sufficient:

import fractions

def divide( numer, denom ):
  return fractions.Fraction( numer, denom )

Currently my library just destructures a fraction into a pair of integers (numer, denom) and exports to python, then uses pure python wrapper code to reassemble the fraction, but this causes all sorts of organizational issues.

Prior attempts

The following is my latest (clearly wrong) attempt.

pub fn export_fraction( numer: isize, denom: isize  ) 
        -> 
        PyResult< PyAny >  {

        Python::with_gil(|py| {
            let frac = py.import("fractions").ok().unwrap();
            Ok(frac.call_method("Fraction",( numer, denom),None) )

        }) 
} 

The error message is a mismatch for the return type; I've tried playing around with it but my ability to parse the various Result, PyResult, etc. types is too nubile.

error[E0308]: mismatched types
  --> src/simplex_filtered.rs:70:9
   |
68 |           PyResult< PyAny >  {
   |           ----------------- expected `Result<pyo3::PyAny, PyErr>` because of return type
69 |
70 | /         Python::with_gil(|py| {
71 | |             let frac = py.import("fractions").ok().unwrap();
72 | |             Ok(frac.call_method("Fraction",( numer, denom),None) )
73 | |
74 | |         }) 
   | |__________^ expected `Result<PyAny, PyErr>`, found `Result<Result<&PyAny, PyErr>, _>`
   |
   = note: expected enum `Result<pyo3::PyAny, PyErr>`
              found enum `Result<Result<&pyo3::PyAny, PyErr>, _>`


Solution

  • PyAny is a type that only exists within the Python environment so it has to be a reference, either one bound to a current GIL, but you have to pass in the Python<'py> instance in that case:

    pub fn export_fraction_with_current_gil<'py>(py: Python<'py>, numer: isize, denom: isize) -> PyResult<&'py PyAny> {
        let frac = py.import("fractions")?;
        frac.call_method("Fraction", (numer, denom), None)
    }
    

    Or you use Py which is "A GIL-independent reference to an object allocated on the Python heap.":

    pub fn export_fraction(numer: isize, denom: isize) -> PyResult<Py<PyAny>> {
        Python::with_gil(|py| {
            let frac = py.import("fractions")?;
            frac.call_method("Fraction", (numer, denom), None)
                .map(Into::into)
        })
    }