Skip to content

Add conversion table to guide #937

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

Merged
merged 1 commit into from
May 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 102 additions & 4 deletions guide/src/conversions.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,104 @@
# Type Conversions

In this portion of the guide we'll talk about the mapping of Python types to Rust types offered by PyO3, as well as the traits available to perform conversions between them.

## Mapping of Rust types to Python types

When writing functions callable from Python (such as a `#[pyfunction]` or in a `#[pymethods]` block), the trait `FromPyObject` is required for function arguments, and `IntoPy<PyObject>` is required for function return values.

Consult the tables in the following section to find the Rust types provided by PyO3 which implement these traits.

### Argument Types

When accepting a function argument, it is possible to either use Rust library types or PyO3's Python-native types. (See the next section for discussion on when to use each.)

The table below contains the Python type and the corresponding function argument types that will accept them:

| Python | Rust | Rust (Python-native) |
| ------------- |:-------------------------------:|:--------------------:|
| `object` | - | `&PyAny` |
| `str` | `String`, `Cow<str>`, `&str` | `&PyUnicode` |
| `bytes` | `Vec<u8>`, `&[u8]` | `&PyBytes` |
| `bool` | `bool` | `&PyBool` |
| `int` | Any integer type (`i32`, `u32`, `usize`, etc) | `&PyLong` |
| `float` | `f32`, `f64` | `&PyFloat` |
| `complex` | `num_complex::Complex`[^1] | `&PyComplex` |
| `list[T]` | `Vec<T>` | `&PyList` |
| `dict[K, V]` | `HashMap<K, V>`, `BTreeMap<K, V>` | `&PyDict` |
| `tuple[T, U]` | `(T, U)`, `Vec<T>` | `&PyTuple` |
| `set[T]` | `HashSet<T>`, `BTreeSet<T>` | `&PySet` |
| `frozenset[T]` | `HashSet<T>`, `BTreeSet<T>` | `&PyFrozenSet` |
| `bytearray` | `Vec<u8>` | `&PyByteArray` |
| `slice` | - | `&PySlice` |
| `type` | - | `&PyType` |
| `module` | - | `&PyModule` |
| `datetime.datetime` | - | `&PyDateTime` |
| `datetime.date` | - | `&PyDate` |
| `datetime.time` | - | `&PyTime` |
| `datetime.tzinfo` | - | `&PyTzInfo` |
| `datetime.timedelta` | - | `&PyDelta` |
| `typing.Optional[T]` | `Option<T>` | - |
| `typing.Sequence[T]` | `Vec<T>` | `&PySequence` |
| `typing.Iterator[Any]` | - | `&PyIterator` |

There are also a few special types related to the GIL and Rust-defined `#[pyclass]`es which may come in useful:

| What | Description |
| ------------- | ------------------------------- |
| `Python` | A GIL token, used to pass to PyO3 constructors to prove ownership of the GIL |
| `PyObject` | A Python object isolated from the GIL lifetime. This can be sent to other threads. To call Python APIs using this object, it must be used with `AsPyRef::as_ref` to get a `&PyAny` reference. |
| `Py<T>` | Same as above, for a specific Python type or `#[pyclass]` T. |
| `&PyCell<T>` | A `#[pyclass]` value owned by Python. |
| `PyRef<T>` | A `#[pyclass]` borrowed immutably. |
| `PyRefMut<T>` | A `#[pyclass]` borrowed mutably. |

For more detail on accepting `#[pyclass]` values as function arguments, see [the section of this guide on Python Classes](class.md).

#### Using Rust library types vs Python-native types

Using Rust library types as function arguments will incur a conversion cost compared to using the Python-native types. Using the Python-native types is almost zero-cost (they just require a type check similar to the Python builtin function `isinstance()`).

However, once that conversion cost has been paid, the Rust standard library types offer a number of benefits:
- You can write functionality in native-speed Rust code (free of Python's runtime costs).
- You get better interoperability with the rest of the Rust ecosystem.
- You can use `Python::allow_threads` to release the Python GIL and let other Python threads make progress while your Rust code is executing.
- You also benefit from stricter type checking. For example you can specify `Vec<i32>`, which will only accept a Python `list` containing integers. The Python-native equivalent, `&PyList`, would accept a Python `list` containing Python objects of any type.

For most PyO3 usage the conversion cost is worth paying to get these benefits. As always, if you're not sure it's worth it in your case, benchmark it!

### Returning Rust values to Python

When returning values from functions callable from Python, Python-native types (`&PyAny`, `&PyDict` etc.) can be used with zero cost.

Because these types are references, in some situations the Rust compiler may ask for lifetime annotations. If this is the case, you should use `Py<PyAny>`, `Py<PyDict>` etc. instead - which are also zero-cost and can be created from the native types with an `.into()` conversion.

If your function is fallible, it should return `PyResult<T>`, which will raise a `Python` exception if the `Err` variant is returned.

Finally, the following Rust types are also able to convert to Python as return values:

| Rust type | Resulting Python Type |
| ------------- |:-------------------------------:|
| `String` | `str` |
| `&str` | `str` |
| `bool` | `bool` |
| Any integer type (`i32`, `u32`, `usize`, etc) | `int` |
| `f32`, `f64` | `float` |
| `Option<T>` | `Optional[T]` |
| `(T, U)` | `Tuple[T, U]` |
| `Vec<T>` | `List[T]` |
| `HashMap<K, V>` | `Dict[K, V]` |
| `BTreeMap<K, V>` | `Dict[K, V]` |
| `HashSet<T>` | `Set[T]` |
| `BTreeSet<T>` | `Set[T]` |
| `&PyCell<T: PyClass>` | `T` |
| `PyRef<T: PyClass>` | `T` |
| `PyRefMut<T: PyClass>` | `T` |

## Traits

PyO3 provides some handy traits to convert between Python types and Rust types.

## `.extract()` and the `FromPyObject` trait
### `.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
Expand All @@ -24,14 +120,14 @@ and [`PyRefMut`]. They work like the reference wrappers of
`std::cell::RefCell` and ensure (at runtime) that Rust borrows are allowed.


## The `ToPyObject` trait
### 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
### `*args` and `**kwargs` for Python object calls

There are several ways how to pass positional and keyword arguments to a Python object call.
[`PyAny`] provides two methods:
Expand Down Expand Up @@ -120,7 +216,7 @@ fn main() {
}
```

## `FromPy<T>` and `IntoPy<T>`
### `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.
Expand All @@ -142,3 +238,5 @@ Eventually, traits such as [`ToPyObject`] will be replaced by this trait and a [

[`PyRef`]: https://pyo3.rs/master/doc/pyo3/pycell/struct.PyRef.html
[`PyRefMut`]: https://pyo3.rs/master/doc/pyo3/pycell/struct.PyRefMut.html

[^1]: Requires the `num-complex` optional feature.
6 changes: 4 additions & 2 deletions guide/src/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ arguments and intermediate values.
These types all implement `Deref<Target = PyAny>`, so they all expose the same
methods which can be found on `PyAny`.

To see all Python types exposed by `PyO3` you should consult the
[`pyo3::types`] module.

**Conversions:**

```rust
Expand Down Expand Up @@ -232,7 +235,6 @@ borrows, analog to `Ref` and `RefMut` used by `RefCell`.
on types like `Py<T>` and `PyAny` to get a reference quickly.



## Related traits and types

### `PyClass`
Expand All @@ -245,9 +247,9 @@ usually defined using the `#[pyclass]` macro.
This trait marks structs that mirror native Python types, such as `PyList`.



[eval]: https://docs.rs/pyo3/latest/pyo3/struct.Python.html#method.eval
[clone_ref]: https://docs.rs/pyo3/latest/pyo3/struct.PyObject.html#method.clone_ref
[pyo3::types]: https://pyo3.rs/master/doc/pyo3/types/index.html
[PyAny]: https://docs.rs/pyo3/latest/pyo3/types/struct.PyAny.html
[PyList_append]: https://docs.rs/pyo3/latest/pyo3/types/struct.PyList.html#method.append
[RefCell]: https://doc.rust-lang.org/std/cell/struct.RefCell.html
32 changes: 26 additions & 6 deletions tests/test_bytes.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use pyo3::prelude::*;
use pyo3::types::PyBytes;
use pyo3::wrap_pyfunction;

mod common;
Expand All @@ -13,10 +14,29 @@ fn test_pybytes_bytes_conversion() {
let gil = Python::acquire_gil();
let py = gil.python();

let bytes_pybytes_conversion = wrap_pyfunction!(bytes_pybytes_conversion)(py);
py_assert!(
py,
bytes_pybytes_conversion,
"bytes_pybytes_conversion(b'Hello World') == b'Hello World'"
);
let f = wrap_pyfunction!(bytes_pybytes_conversion)(py);
py_assert!(py, f, "f(b'Hello World') == b'Hello World'");
}

#[pyfunction]
fn bytes_vec_conversion(py: Python, bytes: Vec<u8>) -> &PyBytes {
PyBytes::new(py, bytes.as_slice())
}

#[test]
fn test_pybytes_vec_conversion() {
let gil = Python::acquire_gil();
let py = gil.python();

let f = wrap_pyfunction!(bytes_vec_conversion)(py);
py_assert!(py, f, "f(b'Hello World') == b'Hello World'");
}

#[test]
fn test_bytearray_vec_conversion() {
let gil = Python::acquire_gil();
let py = gil.python();

let f = wrap_pyfunction!(bytes_vec_conversion)(py);
py_assert!(py, f, "f(bytearray(b'Hello World')) == b'Hello World'");
}