4.8 KiB
Type Conversions
PyO3 provides some handy traits to convert between Python types and Rust types.
.extract()
and the FromPyObject
trait
The easiest way to convert a Python object to a Rust value is using
.extract()
. It returns a PyResult
with a type error if the conversion
fails, so usually you will use something like
let v: Vec<i32> = obj.extract()?;
This method is available for many Python object types, and can produce a wide
variety of Rust types, which you can check out in the implementor list of
FromPyObject
.
FromPyObject
is also implemented for your own Rust types wrapped as Python
objects (see the chapter about classes). There, in order to both be
able to operate on mutable references and satisfy Rust's rules of non-aliasing
mutable references, you have to extract the PyO3 reference wrappers PyRef
and PyRefMut
. They work like the reference wrappers of
std::cell::RefCell
and ensure (at runtime) that Rust borrows are allowed.
The ToPyObject
trait
ToPyObject
is a conversion trait that allows various objects to be
converted into PyObject
. IntoPy<PyObject>
serves the
same purpose, except that it consumes self
.
*args
and **kwargs
for Python object calls
There are several ways how to pass positional and keyword arguments to a Python object call.
The ObjectProtocol
trait provides two methods:
call
- call any callable Python object.call_method
- call a specific method on the object, shorthand forget_attr
thencall
.
Both methods need args
and kwargs
arguments, but there are variants for less
complex calls, such as call1
for only args
and call0
for no arguments at all.
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyTuple};
struct SomeObject;
impl SomeObject {
fn new(py: Python) -> PyObject {
PyDict::new(py).to_object(py)
}
}
fn main() {
let arg1 = "arg1";
let arg2 = "arg2";
let arg3 = "arg3";
let gil = Python::acquire_gil();
let py = gil.python();
let obj = SomeObject::new(py);
// call object without empty arguments
obj.call0(py);
// call object with PyTuple
let args = PyTuple::new(py, &[arg1, arg2, arg3]);
obj.call1(py, args);
// pass arguments as rust tuple
let args = (arg1, arg2, arg3);
obj.call1(py, args);
}
kwargs
can be None
or Some(&PyDict)
. You can use the
IntoPyDict
trait to convert other dict-like containers,
e.g. HashMap
or BTreeMap
, as well as tuples with up to 10 elements and
Vec
s where each element is a two-element tuple.
use pyo3::prelude::*;
use pyo3::types::{IntoPyDict, PyDict};
use std::collections::HashMap;
struct SomeObject;
impl SomeObject {
fn new(py: Python) -> PyObject {
PyDict::new(py).to_object(py)
}
}
fn main() {
let key1 = "key1";
let val1 = 1;
let key2 = "key2";
let val2 = 2;
let gil = Python::acquire_gil();
let py = gil.python();
let obj = SomeObject::new(py);
// call object with PyDict
let kwargs = [(key1, val1)].into_py_dict(py);
obj.call(py, (), Some(kwargs));
// pass arguments as Vec
let kwargs = vec![(key1, val1), (key2, val2)];
obj.call(py, (), Some(kwargs.into_py_dict(py)));
// pass arguments as HashMap
let mut kwargs = HashMap::<&str, i32>::new();
kwargs.insert(key1, 1);
obj.call(py, (), Some(kwargs.into_py_dict(py)));
}
FromPy<T>
and IntoPy<T>
Many conversions in PyO3 can't use std::convert::From
because they need a GIL token.
The FromPy
trait offers an from_py
method that works just like from
, except for taking a Python<'_>
argument.
I.e. FromPy<T>
could be converting a Rust object into a Python object even though it is called FromPy
- it doesn't say anything about which side of the conversion is a Python object.
Just like From<T>
, if you implement FromPy<T>
you gain a blanket implementation of IntoPy
for free.
Eventually, traits such as ToPyObject
will be replaced by this trait and a FromPy
trait will be added that will implement
IntoPy
, just like with From
and Into
.