Merge branch 'master' into abi3

This commit is contained in:
kngwyu 2020-09-10 00:48:20 +09:00
commit 7a4c5e2960
54 changed files with 1510 additions and 1056 deletions

11
.github/dependabot.yml vendored Normal file
View file

@ -0,0 +1,11 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "cargo" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"

View file

@ -6,6 +6,9 @@ on:
- master
pull_request:
env:
CARGO_TERM_COLOR: always
jobs:
fmt:
runs-on: ubuntu-latest
@ -39,6 +42,7 @@ jobs:
name: python${{ matrix.python-version }}-${{ matrix.platform.python-architecture }} ${{ matrix.platform.os }} ${{ matrix.msrv }}
runs-on: ${{ matrix.platform.os }}
strategy:
fail-fast: false # If one platform fails, allow the rest to keep testing.
matrix:
rust: [stable]
python-version: [3.5, 3.6, 3.7, 3.8, 3.9-dev, pypy3]

View file

@ -6,6 +6,9 @@ on:
- master
release:
env:
CARGO_TERM_COLOR: always
jobs:
deploy:
runs-on: ubuntu-latest

View file

@ -15,6 +15,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Add optional implementations of `ToPyObject`, `IntoPy`, and `FromPyObject` for [hashbrown](https://crates.io/crates/hashbrown)'s `HashMap` and `HashSet` types. The `hashbrown` feature must be enabled for these implementations to be built. [#1114](https://github.com/PyO3/pyo3/pull/1114/)
- Allow other `Result` types when using `#[pyfunction]`. [#1106](https://github.com/PyO3/pyo3/issues/1106).
- Add `#[derive(FromPyObject)]` macro for enums and structs. [#1065](https://github.com/PyO3/pyo3/pull/1065)
- Add macro attribute to `#[pyfn]` and `#[pyfunction]` to pass the module of a Python function to the function
body. [#1143](https://github.com/PyO3/pyo3/pull/1143)
- Add `add_function()` and `add_submodule()` functions to `PyModule` [#1143](https://github.com/PyO3/pyo3/pull/1143)
### Changed
- Exception types have been renamed from e.g. `RuntimeError` to `PyRuntimeError`, and are now only accessible by `&T` or `Py<T>` similar to other Python-native types. The old names continue to exist but are deprecated. [#1024](https://github.com/PyO3/pyo3/pull/1024)
@ -30,6 +33,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Implement `Send + Sync` for `PyErr`. `PyErr::new`, `PyErr::from_type`, `PyException::py_err` and `PyException::into` have had these bounds added to their arguments. [#1067](https://github.com/PyO3/pyo3/pull/1067)
- Change `#[pyproto]` to return NotImplemented for operators for which Python can try a reversed operation. #[1072](https://github.com/PyO3/pyo3/pull/1072)
- `PyModule::add` now uses `IntoPy<PyObject>` instead of `ToPyObject`. #[1124](https://github.com/PyO3/pyo3/pull/1124)
- Add nested modules as `&PyModule` instead of using the wrapper generated by `#[pymodule]`. [#1143](https://github.com/PyO3/pyo3/pull/1143)
### Removed
- Remove `PyString::as_bytes`. [#1023](https://github.com/PyO3/pyo3/pull/1023)
@ -50,6 +54,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Link against libpython on android with `extension-module` set. [#1095](https://github.com/PyO3/pyo3/pull/1095)
- Fix support for both `__add__` and `__radd__` in the `+` operator when both are defined in `PyNumberProtocol`
(and similar for all other reversible operators). [#1107](https://github.com/PyO3/pyo3/pull/1107)
- Associate Python functions with their module by passing the Module and Module name [#1143](https://github.com/PyO3/pyo3/pull/1143)
## [0.11.1] - 2020-06-30
### Added
@ -481,8 +486,8 @@ Yanked
- Initial release
[Unreleased]: https://github.com/pyo3/pyo3/compare/v0.11.1...HEAD
[0.11.0] https://github.com/pyo3/pyo3/compare/v0.11.0...v0.11.1
[0.11.0] https://github.com/pyo3/pyo3/compare/v0.10.1...v0.11.0
[0.11.1]: https://github.com/pyo3/pyo3/compare/v0.11.0...v0.11.1
[0.11.0]: https://github.com/pyo3/pyo3/compare/v0.10.1...v0.11.0
[0.10.1]: https://github.com/pyo3/pyo3/compare/v0.10.0...v0.10.1
[0.10.0]: https://github.com/pyo3/pyo3/compare/v0.9.2...v0.10.0
[0.9.2]: https://github.com/pyo3/pyo3/compare/v0.9.1...v0.9.2

View file

@ -25,7 +25,7 @@ num-complex = { version = "0.3", optional = true }
paste = { version = "0.1.6", optional = true }
pyo3cls = { path = "pyo3cls", version = "=0.11.1", optional = true }
unindent = { version = "0.1.4", optional = true }
hashbrown = { version = "0.8", optional = true }
hashbrown = { version = "0.9", optional = true }
[dev-dependencies]
assert_approx_eq = "1.1.0"

View file

@ -67,7 +67,7 @@ fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
/// A Python module implemented in Rust.
#[pymodule]
fn string_sum(py: Python, m: &PyModule) -> PyResult<()> {
m.add_wrapped(wrap_pyfunction!(sum_as_string))?;
m.add_function(wrap_pyfunction!(sum_as_string))?;
Ok(())
}

21
benches/bench_set.rs Normal file
View file

@ -0,0 +1,21 @@
#![feature(test)]
extern crate test;
use pyo3::prelude::*;
use pyo3::types::PySet;
use test::Bencher;
#[bench]
fn iter_set(b: &mut Bencher) {
let gil = Python::acquire_gil();
let py = gil.python();
const LEN: usize = 100_000;
let set = PySet::new(py, &(0..LEN).collect::<Vec<_>>()).unwrap();
let mut sum = 0;
b.iter(|| {
for x in set.iter() {
let i: u64 = x.extract().unwrap();
sum += i;
}
});
}

View file

@ -1,2 +1,9 @@
comment: off
coverage:
status:
project:
default:
target: auto
# Allow a tiny drop of overall project coverage in PR to reduce spurious failures.
threshold: 0.25%

View file

@ -215,29 +215,29 @@ impl TzClass {
#[pymodule]
fn datetime(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add_wrapped(wrap_pyfunction!(make_date))?;
m.add_wrapped(wrap_pyfunction!(get_date_tuple))?;
m.add_wrapped(wrap_pyfunction!(date_from_timestamp))?;
m.add_wrapped(wrap_pyfunction!(make_time))?;
m.add_wrapped(wrap_pyfunction!(get_time_tuple))?;
m.add_wrapped(wrap_pyfunction!(make_delta))?;
m.add_wrapped(wrap_pyfunction!(get_delta_tuple))?;
m.add_wrapped(wrap_pyfunction!(make_datetime))?;
m.add_wrapped(wrap_pyfunction!(get_datetime_tuple))?;
m.add_wrapped(wrap_pyfunction!(datetime_from_timestamp))?;
m.add_function(wrap_pyfunction!(make_date))?;
m.add_function(wrap_pyfunction!(get_date_tuple))?;
m.add_function(wrap_pyfunction!(date_from_timestamp))?;
m.add_function(wrap_pyfunction!(make_time))?;
m.add_function(wrap_pyfunction!(get_time_tuple))?;
m.add_function(wrap_pyfunction!(make_delta))?;
m.add_function(wrap_pyfunction!(get_delta_tuple))?;
m.add_function(wrap_pyfunction!(make_datetime))?;
m.add_function(wrap_pyfunction!(get_datetime_tuple))?;
m.add_function(wrap_pyfunction!(datetime_from_timestamp))?;
// Python 3.6+ functions
#[cfg(Py_3_6)]
{
m.add_wrapped(wrap_pyfunction!(time_with_fold))?;
m.add_function(wrap_pyfunction!(time_with_fold))?;
#[cfg(not(PyPy))]
{
m.add_wrapped(wrap_pyfunction!(get_time_tuple_fold))?;
m.add_wrapped(wrap_pyfunction!(get_datetime_tuple_fold))?;
m.add_function(wrap_pyfunction!(get_time_tuple_fold))?;
m.add_function(wrap_pyfunction!(get_datetime_tuple_fold))?;
}
}
m.add_wrapped(wrap_pyfunction!(issue_219))?;
m.add_function(wrap_pyfunction!(issue_219))?;
m.add_class::<TzClass>()?;
Ok(())

View file

@ -31,7 +31,7 @@ fn double(x: i32) -> i32 {
#[pymodule]
fn othermod(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add_wrapped(wrap_pyfunction!(double))?;
m.add_function(wrap_pyfunction!(double))?;
m.add_class::<ModClass>()?;

View file

@ -56,8 +56,8 @@ fn count_line(line: &str, needle: &str) -> usize {
#[pymodule]
fn word_count(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add_wrapped(wrap_pyfunction!(search))?;
m.add_wrapped(wrap_pyfunction!(search_sequential))?;
m.add_wrapped(wrap_pyfunction!(search_sequential_allow_threads))?;
m.add_function(wrap_pyfunction!(search_sequential))?;
m.add_function(wrap_pyfunction!(search_sequential_allow_threads))?;
Ok(())
}

View file

@ -7,7 +7,10 @@
- [Python Modules](module.md)
- [Python Functions](function.md)
- [Python Classes](class.md)
- [Class customizations](class/protocols.md)
- [Type Conversions](conversions.md)
- [Mapping of Rust types to Python types](conversions/tables.md)]
- [Conversion traits](conversions/traits.md)]
- [Python Exceptions](exception.md)
- [Calling Python from Rust](python_from_rust.md)
- [GIL, mutability and object types](types.md)
@ -20,7 +23,8 @@
---
[Appendix A: PyO3 and rust-cpython](rust_cpython.md)
[Appendix B: Migration Guide](migration.md)
[Appendix A: Migration Guide](migration.md)
[Appendix B: PyO3 and rust-cpython](rust_cpython.md)
[Appendix C: Trait bounds](trait_bounds.md)
[Appendix D: Logging](logging.md)
[CHANGELOG](changelog.md)

1
guide/src/changelog.md Normal file
View file

@ -0,0 +1 @@
{{#include ../../CHANGELOG.md}}

View file

@ -15,7 +15,7 @@ For ease of discovery, below is a list of all custom attributes with links to th
- [`#[call]`](#callable-objects)
- [`#[classattr]`](#class-attributes)
- [`#[args]`](#method-arguments)
- [`#[pyproto]`](#class-customizations)
- [`#[pyproto]`](class/protocols.html)
## Defining a new class
@ -695,307 +695,6 @@ num=44, debug=false
num=-1, debug=false
```
## Class customizations
Python's object model defines several protocols for different object behavior, like sequence,
mapping or number protocols. PyO3 defines separate traits for each of them. To provide specific
Python object behavior, you need to implement the specific trait for your struct. Important note,
each protocol implementation block has to be annotated with the `#[pyproto]` attribute.
All `#[pyproto]` methods which can be defined below can return `T` instead of `PyResult<T>` if the
method implementation is infallible. In addition, if the return type is `()`, it can be omitted altogether.
### Basic object customization
The [`PyObjectProtocol`] trait provides several basic customizations.
#### Attribute access
To customize object attribute access, define the following methods:
* `fn __getattr__(&self, name: FromPyObject) -> PyResult<impl IntoPy<PyObject>>`
* `fn __setattr__(&mut self, name: FromPyObject, value: FromPyObject) -> PyResult<()>`
* `fn __delattr__(&mut self, name: FromPyObject) -> PyResult<()>`
Each method corresponds to Python's `self.attr`, `self.attr = value` and `del self.attr` code.
#### String Conversions
* `fn __repr__(&self) -> PyResult<impl ToPyObject<ObjectType=PyString>>`
* `fn __str__(&self) -> PyResult<impl ToPyObject<ObjectType=PyString>>`
Possible return types for `__str__` and `__repr__` are `PyResult<String>` or `PyResult<PyString>`.
* `fn __bytes__(&self) -> PyResult<PyBytes>`
Provides the conversion to `bytes`.
* `fn __format__(&self, format_spec: &str) -> PyResult<impl ToPyObject<ObjectType=PyString>>`
Special method that is used by the `format()` builtin and the `str.format()` method.
Possible return types are `PyResult<String>` or `PyResult<PyString>`.
#### Comparison operators
* `fn __richcmp__(&self, other: impl FromPyObject, op: CompareOp) -> PyResult<impl ToPyObject>`
Overloads Python comparison operations (`==`, `!=`, `<`, `<=`, `>`, and `>=`).
The `op` argument indicates the comparison operation being performed.
The return type will normally be `PyResult<bool>`, but any Python object can be returned.
If `other` is not of the type specified in the signature, the generated code will
automatically `return NotImplemented`.
* `fn __hash__(&self) -> PyResult<impl PrimInt>`
Objects that compare equal must have the same hash value.
The return type must be `PyResult<T>` where `T` is one of Rust's primitive integer types.
#### Other methods
* `fn __bool__(&self) -> PyResult<bool>`
Determines the "truthyness" of the object.
### Emulating numeric types
The [`PyNumberProtocol`] trait allows [emulate numeric types](https://docs.python.org/3/reference/datamodel.html#emulating-numeric-types).
* `fn __add__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `fn __sub__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `fn __mul__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `fn __matmul__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `fn __truediv__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `fn __floordiv__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `fn __mod__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `fn __divmod__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `fn __pow__(lhs: impl FromPyObject, rhs: impl FromPyObject, modulo: Option<impl FromPyObject>) -> PyResult<impl ToPyObject>`
* `fn __lshift__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `fn __rshift__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `fn __and__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `fn __or__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `fn __xor__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
These methods are called to implement the binary arithmetic operations
(`+`, `-`, `*`, `@`, `/`, `//`, `%`, `divmod()`, `pow()` and `**`, `<<`, `>>`, `&`, `^`, and `|`).
If `rhs` is not of the type specified in the signature, the generated code
will automatically `return NotImplemented`. This is not the case for `lhs`
which must match signature or else raise a TypeError.
The reflected operations are also available:
* `fn __radd__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `fn __rsub__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `fn __rmul__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `fn __rmatmul__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `fn __rtruediv__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `fn __rfloordiv__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `fn __rmod__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `fn __rdivmod__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `fn __rpow__(lhs: impl FromPyObject, rhs: impl FromPyObject, modulo: Option<impl FromPyObject>) -> PyResult<impl ToPyObject>`
* `fn __rlshift__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `fn __rrshift__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `fn __rand__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `fn __ror__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `fn __rxor__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
The code generated for these methods expect that all arguments match the
signature, or raise a TypeError.
*Note*: Currently implementing the method for a binary arithmetic operations
(e.g, `__add__`) shadows the reflected operation (e.g, `__radd__`). This is
being addressed in [#844](https://github.com/PyO3/pyo3/issues/844). to make
these methods
This trait also has support the augmented arithmetic assignments (`+=`, `-=`,
`*=`, `@=`, `/=`, `//=`, `%=`, `**=`, `<<=`, `>>=`, `&=`, `^=`, `|=`):
* `fn __iadd__(&'p mut self, other: impl FromPyObject) -> PyResult<()>`
* `fn __isub__(&'p mut self, other: impl FromPyObject) -> PyResult<()>`
* `fn __imul__(&'p mut self, other: impl FromPyObject) -> PyResult<()>`
* `fn __imatmul__(&'p mut self, other: impl FromPyObject) -> PyResult<()>`
* `fn __itruediv__(&'p mut self, other: impl FromPyObject) -> PyResult<()>`
* `fn __ifloordiv__(&'p mut self, other: impl FromPyObject) -> PyResult<()>`
* `fn __imod__(&'p mut self, other: impl FromPyObject) -> PyResult<()>`
* `fn __ipow__(&'p mut self, other: impl FromPyObject) -> PyResult<()>`
* `fn __ilshift__(&'p mut self, other: impl FromPyObject) -> PyResult<()>`
* `fn __irshift__(&'p mut self, other: impl FromPyObject) -> PyResult<()>`
* `fn __iand__(&'p mut self, other: impl FromPyObject) -> PyResult<()>`
* `fn __ior__(&'p mut self, other: impl FromPyObject) -> PyResult<()>`
* `fn __ixor__(&'p mut self, other: impl FromPyObject) -> PyResult<()>`
The following methods implement the unary arithmetic operations (`-`, `+`, `abs()` and `~`):
* `fn __neg__(&'p self) -> PyResult<impl ToPyObject>`
* `fn __pos__(&'p self) -> PyResult<impl ToPyObject>`
* `fn __abs__(&'p self) -> PyResult<impl ToPyObject>`
* `fn __invert__(&'p self) -> PyResult<impl ToPyObject>`
Support for coercions:
* `fn __complex__(&'p self) -> PyResult<impl ToPyObject>`
* `fn __int__(&'p self) -> PyResult<impl ToPyObject>`
* `fn __float__(&'p self) -> PyResult<impl ToPyObject>`
Other:
* `fn __index__(&'p self) -> PyResult<impl ToPyObject>`
* `fn __round__(&'p self, ndigits: Option<impl FromPyObject>) -> PyResult<impl ToPyObject>`
### Garbage Collector Integration
If your type owns references to other Python objects, you will need to
integrate with Python's garbage collector so that the GC is aware of
those references.
To do this, implement the [`PyGCProtocol`] trait for your struct.
It includes two methods `__traverse__` and `__clear__`.
These correspond to the slots `tp_traverse` and `tp_clear` in the Python C API.
`__traverse__` must call `visit.call()` for each reference to another Python object.
`__clear__` must clear out any mutable references to other Python objects
(thus breaking reference cycles). Immutable references do not have to be cleared,
as every cycle must contain at least one mutable reference.
Example:
```rust
extern crate pyo3;
use pyo3::prelude::*;
use pyo3::PyTraverseError;
use pyo3::gc::{PyGCProtocol, PyVisit};
#[pyclass]
struct ClassWithGCSupport {
obj: Option<PyObject>,
}
#[pyproto]
impl PyGCProtocol for ClassWithGCSupport {
fn __traverse__(&self, visit: PyVisit) -> Result<(), PyTraverseError> {
if let Some(ref obj) = self.obj {
visit.call(obj)?
}
Ok(())
}
fn __clear__(&mut self) {
// Clear reference, this decrements ref counter.
self.obj = None;
}
}
```
Special protocol trait implementations have to be annotated with the `#[pyproto]` attribute.
It is also possible to enable GC for custom classes using the `gc` parameter of the `pyclass` attribute.
i.e. `#[pyclass(gc)]`. In that case instances of custom class participate in Python garbage
collection, and it is possible to track them with `gc` module methods. When using the `gc` parameter,
it is *required* to implement the `PyGCProtocol` trait, failure to do so will result in an error
at compile time:
```compile_fail
#[pyclass(gc)]
struct GCTracked {} // Fails because it does not implement PyGCProtocol
```
### Iterator Types
Iterators can be defined using the
[`PyIterProtocol`](https://docs.rs/pyo3/latest/pyo3/class/iter/trait.PyIterProtocol.html) trait.
It includes two methods `__iter__` and `__next__`:
* `fn __iter__(slf: PyRefMut<Self>) -> PyResult<impl IntoPy<PyObject>>`
* `fn __next__(slf: PyRefMut<Self>) -> PyResult<Option<impl IntoPy<PyObject>>>`
Returning `None` from `__next__` indicates that that there are no further items.
These two methods can be take either `PyRef<Self>` or `PyRefMut<Self>` as their
first argument, so that mutable borrow can be avoided if needed.
Example:
```rust
use pyo3::prelude::*;
use pyo3::PyIterProtocol;
#[pyclass]
struct MyIterator {
iter: Box<Iterator<Item = PyObject> + Send>,
}
#[pyproto]
impl PyIterProtocol for MyIterator {
fn __iter__(slf: PyRef<Self>) -> PyRef<Self> {
slf
}
fn __next__(mut slf: PyRefMut<Self>) -> Option<PyObject> {
slf.iter.next()
}
}
```
In many cases you'll have a distinction between the type being iterated over (i.e. the *iterable*) and the iterator it
provides. In this case, you should implement `PyIterProtocol` for both the iterable and the iterator, but the iterable
only needs to support `__iter__()` while the iterator must support both `__iter__()` and `__next__()`. The default
implementations in `PyIterProtocol` will ensure that the objects behave correctly in Python. For example:
```rust
# use pyo3::prelude::*;
# use pyo3::PyIterProtocol;
#[pyclass]
struct Iter {
inner: std::vec::IntoIter<usize>,
}
#[pyproto]
impl PyIterProtocol for Iter {
fn __iter__(slf: PyRef<Self>) -> PyRef<Self> {
slf
}
fn __next__(mut slf: PyRefMut<Self>) -> Option<usize> {
slf.inner.next()
}
}
#[pyclass]
struct Container {
iter: Vec<usize>,
}
#[pyproto]
impl PyIterProtocol for Container {
fn __iter__(slf: PyRef<Self>) -> PyResult<Py<Iter>> {
let iter = Iter {
inner: slf.iter.clone().into_iter(),
};
Py::new(slf.py(), iter)
}
}
# let gil = Python::acquire_gil();
# let py = gil.python();
# let inst = pyo3::PyCell::new(
# py,
# Container {
# iter: vec![1, 2, 3, 4],
# },
# )
# .unwrap();
# pyo3::py_run!(py, inst, "assert list(inst) == [1, 2, 3, 4]");
# pyo3::py_run!(py, inst, "assert list(iter(iter(inst))) == [1, 2, 3, 4]");
```
For more details on Python's iteration protocols, check out [the "Iterator Types" section of the library
documentation](https://docs.python.org/3/library/stdtypes.html#iterator-types).
#### Returning a value from iteration
This guide has so far shown how to use `Option<T>` to implement yielding values during iteration.
In Python a generator can also return a value. To express this in Rust, PyO3 provides the
[`IterNextOutput`](https://docs.rs/pyo3/latest/pyo3/class/iter/enum.IterNextOutput.html) enum to
both `Yield` values and `Return` a final value - see its docs for further details and an example.
## How methods are implemented
Users should be able to define a `#[pyclass]` with or without `#[pymethods]`, while PyO3 needs a
@ -1089,8 +788,6 @@ impl pyo3::pyclass::PyClassSend for MyClass {
[`GILGuard`]: https://docs.rs/pyo3/latest/pyo3/struct.GILGuard.html
[`PyGCProtocol`]: https://docs.rs/pyo3/latest/pyo3/class/gc/trait.PyGCProtocol.html
[`PyObjectProtocol`]: https://docs.rs/pyo3/latest/pyo3/class/basic/trait.PyObjectProtocol.html
[`PyTypeInfo`]: https://docs.rs/pyo3/latest/pyo3/type_object/trait.PyTypeInfo.html
[`PyTypeObject`]: https://docs.rs/pyo3/latest/pyo3/type_object/trait.PyTypeObject.html

View file

@ -0,0 +1,303 @@
## Class customizations
Python's object model defines several protocols for different object behavior, like sequence,
mapping or number protocols. PyO3 defines separate traits for each of them. To provide specific
Python object behavior, you need to implement the specific trait for your struct. Important note,
each protocol implementation block has to be annotated with the `#[pyproto]` attribute.
All `#[pyproto]` methods which can be defined below can return `T` instead of `PyResult<T>` if the
method implementation is infallible. In addition, if the return type is `()`, it can be omitted altogether.
### Basic object customization
The [`PyObjectProtocol`] trait provides several basic customizations.
#### Attribute access
To customize object attribute access, define the following methods:
* `fn __getattr__(&self, name: FromPyObject) -> PyResult<impl IntoPy<PyObject>>`
* `fn __setattr__(&mut self, name: FromPyObject, value: FromPyObject) -> PyResult<()>`
* `fn __delattr__(&mut self, name: FromPyObject) -> PyResult<()>`
Each method corresponds to Python's `self.attr`, `self.attr = value` and `del self.attr` code.
#### String Conversions
* `fn __repr__(&self) -> PyResult<impl ToPyObject<ObjectType=PyString>>`
* `fn __str__(&self) -> PyResult<impl ToPyObject<ObjectType=PyString>>`
Possible return types for `__str__` and `__repr__` are `PyResult<String>` or `PyResult<PyString>`.
* `fn __bytes__(&self) -> PyResult<PyBytes>`
Provides the conversion to `bytes`.
* `fn __format__(&self, format_spec: &str) -> PyResult<impl ToPyObject<ObjectType=PyString>>`
Special method that is used by the `format()` builtin and the `str.format()` method.
Possible return types are `PyResult<String>` or `PyResult<PyString>`.
#### Comparison operators
* `fn __richcmp__(&self, other: impl FromPyObject, op: CompareOp) -> PyResult<impl ToPyObject>`
Overloads Python comparison operations (`==`, `!=`, `<`, `<=`, `>`, and `>=`).
The `op` argument indicates the comparison operation being performed.
The return type will normally be `PyResult<bool>`, but any Python object can be returned.
If `other` is not of the type specified in the signature, the generated code will
automatically `return NotImplemented`.
* `fn __hash__(&self) -> PyResult<impl PrimInt>`
Objects that compare equal must have the same hash value.
The return type must be `PyResult<T>` where `T` is one of Rust's primitive integer types.
#### Other methods
* `fn __bool__(&self) -> PyResult<bool>`
Determines the "truthyness" of the object.
### Emulating numeric types
The [`PyNumberProtocol`] trait allows [emulate numeric types](https://docs.python.org/3/reference/datamodel.html#emulating-numeric-types).
* `fn __add__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `fn __sub__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `fn __mul__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `fn __matmul__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `fn __truediv__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `fn __floordiv__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `fn __mod__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `fn __divmod__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `fn __pow__(lhs: impl FromPyObject, rhs: impl FromPyObject, modulo: Option<impl FromPyObject>) -> PyResult<impl ToPyObject>`
* `fn __lshift__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `fn __rshift__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `fn __and__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `fn __or__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `fn __xor__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
These methods are called to implement the binary arithmetic operations
(`+`, `-`, `*`, `@`, `/`, `//`, `%`, `divmod()`, `pow()` and `**`, `<<`, `>>`, `&`, `^`, and `|`).
If `rhs` is not of the type specified in the signature, the generated code
will automatically `return NotImplemented`. This is not the case for `lhs`
which must match signature or else raise a TypeError.
The reflected operations are also available:
* `fn __radd__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `fn __rsub__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `fn __rmul__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `fn __rmatmul__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `fn __rtruediv__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `fn __rfloordiv__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `fn __rmod__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `fn __rdivmod__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `fn __rpow__(lhs: impl FromPyObject, rhs: impl FromPyObject, modulo: Option<impl FromPyObject>) -> PyResult<impl ToPyObject>`
* `fn __rlshift__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `fn __rrshift__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `fn __rand__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `fn __ror__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
* `fn __rxor__(lhs: impl FromPyObject, rhs: impl FromPyObject) -> PyResult<impl ToPyObject>`
The code generated for these methods expect that all arguments match the
signature, or raise a TypeError.
*Note*: Currently implementing the method for a binary arithmetic operations
(e.g, `__add__`) shadows the reflected operation (e.g, `__radd__`). This is
being addressed in [#844](https://github.com/PyO3/pyo3/issues/844). to make
these methods
This trait also has support the augmented arithmetic assignments (`+=`, `-=`,
`*=`, `@=`, `/=`, `//=`, `%=`, `**=`, `<<=`, `>>=`, `&=`, `^=`, `|=`):
* `fn __iadd__(&'p mut self, other: impl FromPyObject) -> PyResult<()>`
* `fn __isub__(&'p mut self, other: impl FromPyObject) -> PyResult<()>`
* `fn __imul__(&'p mut self, other: impl FromPyObject) -> PyResult<()>`
* `fn __imatmul__(&'p mut self, other: impl FromPyObject) -> PyResult<()>`
* `fn __itruediv__(&'p mut self, other: impl FromPyObject) -> PyResult<()>`
* `fn __ifloordiv__(&'p mut self, other: impl FromPyObject) -> PyResult<()>`
* `fn __imod__(&'p mut self, other: impl FromPyObject) -> PyResult<()>`
* `fn __ipow__(&'p mut self, other: impl FromPyObject) -> PyResult<()>`
* `fn __ilshift__(&'p mut self, other: impl FromPyObject) -> PyResult<()>`
* `fn __irshift__(&'p mut self, other: impl FromPyObject) -> PyResult<()>`
* `fn __iand__(&'p mut self, other: impl FromPyObject) -> PyResult<()>`
* `fn __ior__(&'p mut self, other: impl FromPyObject) -> PyResult<()>`
* `fn __ixor__(&'p mut self, other: impl FromPyObject) -> PyResult<()>`
The following methods implement the unary arithmetic operations (`-`, `+`, `abs()` and `~`):
* `fn __neg__(&'p self) -> PyResult<impl ToPyObject>`
* `fn __pos__(&'p self) -> PyResult<impl ToPyObject>`
* `fn __abs__(&'p self) -> PyResult<impl ToPyObject>`
* `fn __invert__(&'p self) -> PyResult<impl ToPyObject>`
Support for coercions:
* `fn __complex__(&'p self) -> PyResult<impl ToPyObject>`
* `fn __int__(&'p self) -> PyResult<impl ToPyObject>`
* `fn __float__(&'p self) -> PyResult<impl ToPyObject>`
Other:
* `fn __index__(&'p self) -> PyResult<impl ToPyObject>`
* `fn __round__(&'p self, ndigits: Option<impl FromPyObject>) -> PyResult<impl ToPyObject>`
### Garbage Collector Integration
If your type owns references to other Python objects, you will need to
integrate with Python's garbage collector so that the GC is aware of
those references.
To do this, implement the [`PyGCProtocol`] trait for your struct.
It includes two methods `__traverse__` and `__clear__`.
These correspond to the slots `tp_traverse` and `tp_clear` in the Python C API.
`__traverse__` must call `visit.call()` for each reference to another Python object.
`__clear__` must clear out any mutable references to other Python objects
(thus breaking reference cycles). Immutable references do not have to be cleared,
as every cycle must contain at least one mutable reference.
Example:
```rust
extern crate pyo3;
use pyo3::prelude::*;
use pyo3::PyTraverseError;
use pyo3::gc::{PyGCProtocol, PyVisit};
#[pyclass]
struct ClassWithGCSupport {
obj: Option<PyObject>,
}
#[pyproto]
impl PyGCProtocol for ClassWithGCSupport {
fn __traverse__(&self, visit: PyVisit) -> Result<(), PyTraverseError> {
if let Some(ref obj) = self.obj {
visit.call(obj)?
}
Ok(())
}
fn __clear__(&mut self) {
// Clear reference, this decrements ref counter.
self.obj = None;
}
}
```
Special protocol trait implementations have to be annotated with the `#[pyproto]` attribute.
It is also possible to enable GC for custom classes using the `gc` parameter of the `pyclass` attribute.
i.e. `#[pyclass(gc)]`. In that case instances of custom class participate in Python garbage
collection, and it is possible to track them with `gc` module methods. When using the `gc` parameter,
it is *required* to implement the `PyGCProtocol` trait, failure to do so will result in an error
at compile time:
```compile_fail
#[pyclass(gc)]
struct GCTracked {} // Fails because it does not implement PyGCProtocol
```
### Iterator Types
Iterators can be defined using the
[`PyIterProtocol`](https://docs.rs/pyo3/latest/pyo3/class/iter/trait.PyIterProtocol.html) trait.
It includes two methods `__iter__` and `__next__`:
* `fn __iter__(slf: PyRefMut<Self>) -> PyResult<impl IntoPy<PyObject>>`
* `fn __next__(slf: PyRefMut<Self>) -> PyResult<Option<impl IntoPy<PyObject>>>`
Returning `None` from `__next__` indicates that that there are no further items.
These two methods can be take either `PyRef<Self>` or `PyRefMut<Self>` as their
first argument, so that mutable borrow can be avoided if needed.
Example:
```rust
use pyo3::prelude::*;
use pyo3::PyIterProtocol;
#[pyclass]
struct MyIterator {
iter: Box<Iterator<Item = PyObject> + Send>,
}
#[pyproto]
impl PyIterProtocol for MyIterator {
fn __iter__(slf: PyRef<Self>) -> PyRef<Self> {
slf
}
fn __next__(mut slf: PyRefMut<Self>) -> Option<PyObject> {
slf.iter.next()
}
}
```
In many cases you'll have a distinction between the type being iterated over (i.e. the *iterable*) and the iterator it
provides. In this case, you should implement `PyIterProtocol` for both the iterable and the iterator, but the iterable
only needs to support `__iter__()` while the iterator must support both `__iter__()` and `__next__()`. The default
implementations in `PyIterProtocol` will ensure that the objects behave correctly in Python. For example:
```rust
# use pyo3::prelude::*;
# use pyo3::PyIterProtocol;
#[pyclass]
struct Iter {
inner: std::vec::IntoIter<usize>,
}
#[pyproto]
impl PyIterProtocol for Iter {
fn __iter__(slf: PyRef<Self>) -> PyRef<Self> {
slf
}
fn __next__(mut slf: PyRefMut<Self>) -> Option<usize> {
slf.inner.next()
}
}
#[pyclass]
struct Container {
iter: Vec<usize>,
}
#[pyproto]
impl PyIterProtocol for Container {
fn __iter__(slf: PyRef<Self>) -> PyResult<Py<Iter>> {
let iter = Iter {
inner: slf.iter.clone().into_iter(),
};
Py::new(slf.py(), iter)
}
}
# let gil = Python::acquire_gil();
# let py = gil.python();
# let inst = pyo3::PyCell::new(
# py,
# Container {
# iter: vec![1, 2, 3, 4],
# },
# )
# .unwrap();
# pyo3::py_run!(py, inst, "assert list(inst) == [1, 2, 3, 4]");
# pyo3::py_run!(py, inst, "assert list(iter(iter(inst))) == [1, 2, 3, 4]");
```
For more details on Python's iteration protocols, check out [the "Iterator Types" section of the library
documentation](https://docs.python.org/3/library/stdtypes.html#iterator-types).
#### Returning a value from iteration
This guide has so far shown how to use `Option<T>` to implement yielding values during iteration.
In Python a generator can also return a value. To express this in Rust, PyO3 provides the
[`IterNextOutput`](https://docs.rs/pyo3/latest/pyo3/class/iter/enum.IterNextOutput.html) enum to
both `Yield` values and `Return` a final value - see its docs for further details and an example.
[`PyGCProtocol`]: https://docs.rs/pyo3/latest/pyo3/class/gc/trait.PyGCProtocol.html
[`PyGCProtocol`]: https://docs.rs/pyo3/latest/pyo3/class/number/trait.PyNumberProtocol.html
[`PyObjectProtocol`]: https://docs.rs/pyo3/latest/pyo3/class/basic/trait.PyObjectProtocol.html

View file

@ -1,422 +1,3 @@
# 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>`, `hashbrown::HashMap<K, V>`[^2] | `&PyDict` |
| `tuple[T, U]` | `(T, U)`, `Vec<T>` | `&PyTuple` |
| `set[T]` | `HashSet<T>`, `BTreeSet<T>`, `hashbrown::HashSet<T>`[^2] | `&PySet` |
| `frozenset[T]` | `HashSet<T>`, `BTreeSet<T>`, `hashbrown::HashSet<T>`[^2] | `&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 |
| `Py<T>` | A Python object isolated from the GIL lifetime. This can be sent to other threads. |
| `PyObject` | An alias for `Py<PyAny>` |
| `&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. For all of these Python-native types `T`, `Py<T>` can be created from `T` with an `.into()` conversion.
If your function is fallible, it should return `PyResult<T>` or `Result<T, E>` where `E` implements `From<E> for PyErr`. This 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
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
```ignore
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](class.md)). 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.
#### Deriving [`FromPyObject`]
[`FromPyObject`] can be automatically derived for many kinds of structs and enums
if the member types themselves implement `FromPyObject`. This even includes members
with a generic type `T: FromPyObject`. Derivation for empty enums, enum variants and
structs is not supported.
#### Deriving [`FromPyObject`] for structs
```
use pyo3::prelude::*;
#[derive(FromPyObject)]
struct RustyStruct {
my_string: String,
}
```
The derivation generates code that will per default access the attribute `my_string` on
the Python object, i.e. `obj.getattr("my_string")`, and call `extract()` on the attribute.
It is also possible to access the value on the Python object through `obj.get_item("my_string")`
by setting the attribute `pyo3(item)` on the field:
```
use pyo3::prelude::*;
#[derive(FromPyObject)]
struct RustyStruct {
#[pyo3(item)]
my_string: String,
}
```
The argument passed to `getattr` and `get_item` can also be configured:
```
use pyo3::prelude::*;
#[derive(FromPyObject)]
struct RustyStruct {
#[pyo3(item("key"))]
string_in_mapping: String,
#[pyo3(attribute("name"))]
string_attr: String,
}
```
This tries to extract `string_attr` from the attribute `name` and `string_in_mapping`
from a mapping with the key `"key"`. The arguments for `attribute` are restricted to
non-empty string literals while `item` can take any valid literal that implements
`ToBorrowedObject`.
#### Deriving [`FromPyObject`] for tuple structs
Tuple structs are also supported but do not allow customizing the extraction. The input is
always assumed to be a Python tuple with the same length as the Rust type, the `n`th field
is extracted from the `n`th item in the Python tuple.
```
use pyo3::prelude::*;
#[derive(FromPyObject)]
struct RustyTuple(String, String);
```
Tuple structs with a single field are treated as wrapper types which are described in the
following section. To override this behaviour and ensure that the input is in fact a tuple,
specify the struct as
```
use pyo3::prelude::*;
#[derive(FromPyObject)]
struct RustyTuple((String,));
```
#### Deriving [`FromPyObject`] for wrapper types
The `pyo3(transparent)` attribute can be used on structs with exactly one field. This results
in extracting directly from the input object, i.e. `obj.extract()`, rather than trying to access
an item or attribute. This behaviour is enabled per default for newtype structs and tuple-variants
with a single field.
```
use pyo3::prelude::*;
#[derive(FromPyObject)]
struct RustyTransparentTupleStruct(String);
#[derive(FromPyObject)]
#[pyo3(transparent)]
struct RustyTransparentStruct {
inner: String,
}
```
#### Deriving [`FromPyObject`] for enums
The `FromPyObject` derivation for enums generates code that tries to extract the variants in the
order of the fields. As soon as a variant can be extracted succesfully, that variant is returned.
The same customizations and restrictions described for struct derivations apply to enum variants,
i.e. a tuple variant assumes that the input is a Python tuple, and a struct variant defaults to
extracting fields as attributes but can be configured in the same manner. The `transparent`
attribute can be applied to single-field-variants.
```
use pyo3::prelude::*;
#[derive(FromPyObject)]
enum RustyEnum<'a> {
Int(usize), // input is a positive int
String(String), // input is a string
IntTuple(usize, usize), // input is a 2-tuple with positive ints
StringIntTuple(String, usize), // input is a 2-tuple with String and int
Coordinates3d { // needs to be in front of 2d
x: usize,
y: usize,
z: usize,
},
Coordinates2d { // only gets checked if the input did not have `z`
#[pyo3(attribute("x"))]
a: usize,
#[pyo3(attribute("y"))]
b: usize,
},
#[pyo3(transparent)]
CatchAll(&'a PyAny), // This extraction never fails
}
```
If none of the enum variants match, a `PyValueError` containing the names of the
tested variants is returned. The names reported in the error message can be customized
through the `pyo3(annotation = "name")` attribute, e.g. to use conventional Python type
names:
```
use pyo3::prelude::*;
#[derive(FromPyObject)]
enum RustyEnum {
#[pyo3(transparent, annotation = "str")]
String(String),
#[pyo3(transparent, annotation = "int")]
Int(isize),
}
```
If the input is neither a string nor an integer, the error message will be:
`"Can't convert <INPUT> to Union[str, int]"`, where `<INPUT>` is replaced by the type name and
`repr()` of the input object.
#### `#[derive(FromPyObject)]` Container Attributes
- `pyo3(transparent)`
- extract the field directly from the object as `obj.extract()` instead of `get_item()` or
`getattr()`
- Newtype structs and tuple-variants are treated as transparent per default.
- only supported for single-field structs and enum variants
- `pyo3(annotation = "name")`
- changes the name of the failed variant in the generated error message in case of failure.
- e.g. `pyo3("int")` reports the variant's type as `int`.
- only supported for enum variants
#### `#[derive(FromPyObject)]` Field Attributes
- `pyo3(attribute)`, `pyo3(attribute("name"))`
- retrieve the field from an attribute, possibly with a custom name specified as an argument
- argument must be a string-literal.
- `pyo3(item)`, `pyo3(item("key"))`
- retrieve the field from a mapping, possibly with the custom key specified as an argument.
- can be any literal that implements `ToBorrowedObject`
### `IntoPy<T>`
This trait defines the to-python conversion for a Rust type. It is usually implemented as
`IntoPy<PyObject>`, which is the trait needed for returning a value from `#[pyfunction]` and
`#[pymethods]`.
All types in PyO3 implement this trait, as does a `#[pyclass]` which doesn't use `extends`.
Occasionally you may choose to implement this for custom types which are mapped to Python types
_without_ having a unique python type.
```
use pyo3::prelude::*;
struct MyPyObjectWrapper(PyObject);
impl IntoPy<PyObject> for MyPyObjectWrapper {
fn into_py(self, py: Python) -> PyObject {
self.0
}
}
```
### 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.
[`PyAny`] provides two methods:
* `call` - call any callable Python object.
* `call_method` - call a specific method on the object, shorthand for `get_attr` then `call`.
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.
```rust
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.
```rust
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)));
}
```
[`IntoPy`]: https://docs.rs/pyo3/latest/pyo3/conversion/trait.IntoPy.html
[`FromPyObject`]: https://docs.rs/pyo3/latest/pyo3/conversion/trait.FromPyObject.html
[`ToPyObject`]: https://docs.rs/pyo3/latest/pyo3/conversion/trait.ToPyObject.html
[`PyObject`]: https://docs.rs/pyo3/latest/pyo3/type.PyObject.html
[`PyTuple`]: https://docs.rs/pyo3/latest/pyo3/types/struct.PyTuple.html
[`PyAny`]: https://docs.rs/pyo3/latest/pyo3/struct.PyAny.html
[`IntoPyDict`]: https://docs.rs/pyo3/latest/pyo3/types/trait.IntoPyDict.html
[`PyRef`]: https://docs.rs/pyo3/latest/pyo3/pycell/struct.PyRef.html
[`PyRefMut`]: https://docs.rs/pyo3/latest/pyo3/pycell/struct.PyRefMut.html
[^1]: Requires the `num-complex` optional feature.
[^2]: Requires the `hashbrown` optional feature.

View file

@ -0,0 +1,96 @@
## 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>`, `hashbrown::HashMap<K, V>`[^2] | `&PyDict` |
| `tuple[T, U]` | `(T, U)`, `Vec<T>` | `&PyTuple` |
| `set[T]` | `HashSet<T>`, `BTreeSet<T>`, `hashbrown::HashSet<T>`[^2] | `&PySet` |
| `frozenset[T]` | `HashSet<T>`, `BTreeSet<T>`, `hashbrown::HashSet<T>`[^2] | `&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` |
| `typing.Union[...]` | See [`#[derive(FromPyObject)]`](traits.html#deriving-a-hrefhttpsdocsrspyo3latestpyo3conversiontraitfrompyobjecthtmlfrompyobjecta-for-enums) | - |
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 |
| `Py<T>` | A Python object isolated from the GIL lifetime. This can be sent to other threads. |
| `PyObject` | An alias for `Py<PyAny>` |
| `&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. For all of these Python-native types `T`, `Py<T>` can be created from `T` with an `.into()` conversion.
If your function is fallible, it should return `PyResult<T>` or `Result<T, E>` where `E` implements `From<E> for PyErr`. This 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` |
[^1]: Requires the `num-complex` optional feature.
[^2]: Requires the `hashbrown` optional feature.

View file

@ -0,0 +1,324 @@
## Conversion traits
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
```ignore
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](class.md)). 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.
#### Deriving [`FromPyObject`]
[`FromPyObject`] can be automatically derived for many kinds of structs and enums
if the member types themselves implement `FromPyObject`. This even includes members
with a generic type `T: FromPyObject`. Derivation for empty enums, enum variants and
structs is not supported.
#### Deriving [`FromPyObject`] for structs
```
use pyo3::prelude::*;
#[derive(FromPyObject)]
struct RustyStruct {
my_string: String,
}
```
The derivation generates code that will per default access the attribute `my_string` on
the Python object, i.e. `obj.getattr("my_string")`, and call `extract()` on the attribute.
It is also possible to access the value on the Python object through `obj.get_item("my_string")`
by setting the attribute `pyo3(item)` on the field:
```
use pyo3::prelude::*;
#[derive(FromPyObject)]
struct RustyStruct {
#[pyo3(item)]
my_string: String,
}
```
The argument passed to `getattr` and `get_item` can also be configured:
```
use pyo3::prelude::*;
#[derive(FromPyObject)]
struct RustyStruct {
#[pyo3(item("key"))]
string_in_mapping: String,
#[pyo3(attribute("name"))]
string_attr: String,
}
```
This tries to extract `string_attr` from the attribute `name` and `string_in_mapping`
from a mapping with the key `"key"`. The arguments for `attribute` are restricted to
non-empty string literals while `item` can take any valid literal that implements
`ToBorrowedObject`.
#### Deriving [`FromPyObject`] for tuple structs
Tuple structs are also supported but do not allow customizing the extraction. The input is
always assumed to be a Python tuple with the same length as the Rust type, the `n`th field
is extracted from the `n`th item in the Python tuple.
```
use pyo3::prelude::*;
#[derive(FromPyObject)]
struct RustyTuple(String, String);
```
Tuple structs with a single field are treated as wrapper types which are described in the
following section. To override this behaviour and ensure that the input is in fact a tuple,
specify the struct as
```
use pyo3::prelude::*;
#[derive(FromPyObject)]
struct RustyTuple((String,));
```
#### Deriving [`FromPyObject`] for wrapper types
The `pyo3(transparent)` attribute can be used on structs with exactly one field. This results
in extracting directly from the input object, i.e. `obj.extract()`, rather than trying to access
an item or attribute. This behaviour is enabled per default for newtype structs and tuple-variants
with a single field.
```
use pyo3::prelude::*;
#[derive(FromPyObject)]
struct RustyTransparentTupleStruct(String);
#[derive(FromPyObject)]
#[pyo3(transparent)]
struct RustyTransparentStruct {
inner: String,
}
```
#### Deriving [`FromPyObject`] for enums
The `FromPyObject` derivation for enums generates code that tries to extract the variants in the
order of the fields. As soon as a variant can be extracted succesfully, that variant is returned.
This makes it possible to extract Python types like `Union[str, int]`.
The same customizations and restrictions described for struct derivations apply to enum variants,
i.e. a tuple variant assumes that the input is a Python tuple, and a struct variant defaults to
extracting fields as attributes but can be configured in the same manner. The `transparent`
attribute can be applied to single-field-variants.
```
use pyo3::prelude::*;
#[derive(FromPyObject)]
enum RustyEnum<'a> {
Int(usize), // input is a positive int
String(String), // input is a string
IntTuple(usize, usize), // input is a 2-tuple with positive ints
StringIntTuple(String, usize), // input is a 2-tuple with String and int
Coordinates3d { // needs to be in front of 2d
x: usize,
y: usize,
z: usize,
},
Coordinates2d { // only gets checked if the input did not have `z`
#[pyo3(attribute("x"))]
a: usize,
#[pyo3(attribute("y"))]
b: usize,
},
#[pyo3(transparent)]
CatchAll(&'a PyAny), // This extraction never fails
}
```
If none of the enum variants match, a `PyValueError` containing the names of the
tested variants is returned. The names reported in the error message can be customized
through the `pyo3(annotation = "name")` attribute, e.g. to use conventional Python type
names:
```
use pyo3::prelude::*;
#[derive(FromPyObject)]
enum RustyEnum {
#[pyo3(transparent, annotation = "str")]
String(String),
#[pyo3(transparent, annotation = "int")]
Int(isize),
}
```
If the input is neither a string nor an integer, the error message will be:
`"Can't convert <INPUT> to Union[str, int]"`, where `<INPUT>` is replaced by the type name and
`repr()` of the input object.
#### `#[derive(FromPyObject)]` Container Attributes
- `pyo3(transparent)`
- extract the field directly from the object as `obj.extract()` instead of `get_item()` or
`getattr()`
- Newtype structs and tuple-variants are treated as transparent per default.
- only supported for single-field structs and enum variants
- `pyo3(annotation = "name")`
- changes the name of the failed variant in the generated error message in case of failure.
- e.g. `pyo3("int")` reports the variant's type as `int`.
- only supported for enum variants
#### `#[derive(FromPyObject)]` Field Attributes
- `pyo3(attribute)`, `pyo3(attribute("name"))`
- retrieve the field from an attribute, possibly with a custom name specified as an argument
- argument must be a string-literal.
- `pyo3(item)`, `pyo3(item("key"))`
- retrieve the field from a mapping, possibly with the custom key specified as an argument.
- can be any literal that implements `ToBorrowedObject`
### `IntoPy<T>`
This trait defines the to-python conversion for a Rust type. It is usually implemented as
`IntoPy<PyObject>`, which is the trait needed for returning a value from `#[pyfunction]` and
`#[pymethods]`.
All types in PyO3 implement this trait, as does a `#[pyclass]` which doesn't use `extends`.
Occasionally you may choose to implement this for custom types which are mapped to Python types
_without_ having a unique python type.
```
use pyo3::prelude::*;
struct MyPyObjectWrapper(PyObject);
impl IntoPy<PyObject> for MyPyObjectWrapper {
fn into_py(self, py: Python) -> PyObject {
self.0
}
}
```
### 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.
[`PyAny`] provides two methods:
* `call` - call any callable Python object.
* `call_method` - call a specific method on the object, shorthand for `get_attr` then `call`.
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.
```rust
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.
```rust
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)));
}
```
[`IntoPy`]: https://docs.rs/pyo3/latest/pyo3/conversion/trait.IntoPy.html
[`FromPyObject`]: https://docs.rs/pyo3/latest/pyo3/conversion/trait.FromPyObject.html
[`ToPyObject`]: https://docs.rs/pyo3/latest/pyo3/conversion/trait.ToPyObject.html
[`PyObject`]: https://docs.rs/pyo3/latest/pyo3/type.PyObject.html
[`PyTuple`]: https://docs.rs/pyo3/latest/pyo3/types/struct.PyTuple.html
[`PyAny`]: https://docs.rs/pyo3/latest/pyo3/struct.PyAny.html
[`IntoPyDict`]: https://docs.rs/pyo3/latest/pyo3/types/trait.IntoPyDict.html
[`PyRef`]: https://docs.rs/pyo3/latest/pyo3/pycell/struct.PyRef.html
[`PyRefMut`]: https://docs.rs/pyo3/latest/pyo3/pycell/struct.PyRefMut.html

View file

@ -36,7 +36,7 @@ fn double(x: usize) -> usize {
#[pymodule]
fn module_with_functions(py: Python, m: &PyModule) -> PyResult<()> {
m.add_wrapped(wrap_pyfunction!(double)).unwrap();
m.add_function(wrap_pyfunction!(double)).unwrap();
Ok(())
}
@ -65,7 +65,7 @@ fn num_kwds(kwds: Option<&PyDict>) -> usize {
#[pymodule]
fn module_with_functions(py: Python, m: &PyModule) -> PyResult<()> {
m.add_wrapped(wrap_pyfunction!(num_kwds)).unwrap();
m.add_function(wrap_pyfunction!(num_kwds)).unwrap();
Ok(())
}
@ -189,3 +189,47 @@ If you have a static function, you can expose it with `#[pyfunction]` and use [`
[`PyAny::call1`]: https://docs.rs/pyo3/latest/pyo3/struct.PyAny.html#tymethod.call1
[`PyObject`]: https://docs.rs/pyo3/latest/pyo3/type.PyObject.html
[`wrap_pyfunction!`]: https://docs.rs/pyo3/latest/pyo3/macro.wrap_pyfunction.html
### Accessing the module of a function
It is possible to access the module of a `#[pyfunction]` and `#[pyfn]` in the
function body by passing the `pass_module` argument to the attribute:
```rust
use pyo3::wrap_pyfunction;
use pyo3::prelude::*;
#[pyfunction(pass_module)]
fn pyfunction_with_module(module: &PyModule) -> PyResult<&str> {
module.name()
}
#[pymodule]
fn module_with_fn(py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(pyfunction_with_module))
}
# fn main() {}
```
If `pass_module` is set, the first argument **must** be the `&PyModule`. It is then possible to use the module
in the function body.
The same works for `#[pyfn]`:
```rust
use pyo3::wrap_pyfunction;
use pyo3::prelude::*;
#[pymodule]
fn module_with_fn(py: Python, m: &PyModule) -> PyResult<()> {
#[pyfn(m, "module_name", pass_module)]
fn module_name(module: &PyModule) -> PyResult<&str> {
module.name()
}
Ok(())
}
# fn main() {}
```

View file

@ -1,4 +1,4 @@
# The PyO3 Guide
# The PyO3 user guide
Welcome to the PyO3 user guide! This book is a companion to [PyO3's API docs](https://docs.rs/pyo3). It contains examples and documentation to explain all of PyO3's use cases in detail.

View file

@ -35,7 +35,7 @@ fn my_module(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
// A good place to install the Rust -> Python logger.
pyo3_log::init();
m.add_wrapped(wrap_pyfunction!(log_something))?;
m.add_function(wrap_pyfunction!(log_something))?;
Ok(())
}
```

View file

@ -1,7 +1,7 @@
# Appendix B: Migrating from older PyO3 versions
# Migrating from older PyO3 versions
This guide can help you upgrade code through breaking changes from one PyO3 version to the next.
For a detailed list of all changes, see [CHANGELOG.md](https://github.com/PyO3/pyo3/blob/master/CHANGELOG.md)
For a detailed list of all changes, see the [CHANGELOG](changelog.md).
## from 0.11.* to 0.12
@ -248,7 +248,7 @@ impl MyClass {
```
Basically you can return `Self` or `Result<Self>` directly.
For more, see [the constructor section](https://pyo3.rs/master/class.html#constructor) of this guide.
For more, see [the constructor section](class.html#constructor) of this guide.
### PyCell
PyO3 0.9 introduces [`PyCell`], which is a [`RefCell`]-like object wrapper

View file

@ -32,16 +32,22 @@ fn sum_as_string(a: i64, b: i64) -> String {
# fn main() {}
```
The `#[pymodule]` procedural macro attribute takes care of exporting the initialization function of your module to Python. It can take as an argument the name of your module, which must be the name of the `.so` or `.pyd` file; the default is the Rust function's name.
The `#[pymodule]` procedural macro attribute takes care of exporting the initialization function of your
module to Python. It can take as an argument the name of your module, which must be the name of the `.so`
or `.pyd` file; the default is the Rust function's name.
If the name of the module (the default being the function name) does not match the name of the `.so` or `.pyd` file, you will get an import error in Python with the following message:
If the name of the module (the default being the function name) does not match the name of the `.so` or
`.pyd` file, you will get an import error in Python with the following message:
`ImportError: dynamic module does not define module export function (PyInit_name_of_your_module)`
To import the module, either copy the shared library as described in [the README](https://github.com/PyO3/pyo3) or use a tool, e.g. `maturin develop` with [maturin](https://github.com/PyO3/maturin) or `python setup.py develop` with [setuptools-rust](https://github.com/PyO3/setuptools-rust).
To import the module, either copy the shared library as described in [the README](https://github.com/PyO3/pyo3)
or use a tool, e.g. `maturin develop` with [maturin](https://github.com/PyO3/maturin) or
`python setup.py develop` with [setuptools-rust](https://github.com/PyO3/setuptools-rust).
## Documentation
The [Rust doc comments](https://doc.rust-lang.org/stable/book/first-edition/comments.html) of the module initialization function will be applied automatically as the Python docstring of your module.
The [Rust doc comments](https://doc.rust-lang.org/stable/book/first-edition/comments.html) of the module
initialization function will be applied automatically as the Python docstring of your module.
```python
import rust2py
@ -53,7 +59,8 @@ Which means that the above Python code will print `This module is implemented in
## Modules as objects
In Python, modules are first class objects. This means that you can store them as values or add them to dicts or other modules:
In Python, modules are first class objects. This means that you can store them as values or add them to
dicts or other modules:
```rust
use pyo3::prelude::*;
@ -65,15 +72,16 @@ fn subfunction() -> String {
"Subfunction".to_string()
}
#[pymodule]
fn submodule(_py: Python, module: &PyModule) -> PyResult<()> {
module.add_wrapped(wrap_pyfunction!(subfunction))?;
fn init_submodule(module: &PyModule) -> PyResult<()> {
module.add_function(wrap_pyfunction!(subfunction))?;
Ok(())
}
#[pymodule]
fn supermodule(_py: Python, module: &PyModule) -> PyResult<()> {
module.add_wrapped(wrap_pymodule!(submodule))?;
fn supermodule(py: Python, module: &PyModule) -> PyResult<()> {
let submod = PyModule::new(py, "submodule")?;
init_submodule(submod)?;
module.add_submodule(submod)?;
Ok(())
}
@ -86,3 +94,5 @@ fn supermodule(_py: Python, module: &PyModule) -> PyResult<()> {
```
This way, you can create a module hierarchy within a single extension module.
It is not necessary to add `#[pymodule]` on nested modules, this is only required on the top-level module.

View file

@ -1,8 +1,6 @@
# Appendix A: PyO3 and rust-cpython
# PyO3 and rust-cpython
PyO3 began as fork of [rust-cpython](https://github.com/dgrunwald/rust-cpython) when rust-cpython wasn't maintained. Over the time PyO3 has become fundamentally different from rust-cpython.
This chapter is based on the discussion in [PyO3/pyo3#55](https://github.com/PyO3/pyo3/issues/55).
PyO3 began as fork of [rust-cpython](https://github.com/dgrunwald/rust-cpython) when rust-cpython wasn't maintained. Over time PyO3 has become fundamentally different from rust-cpython.
## Macros

View file

@ -1,6 +1,6 @@
# Using in Python a Rust function with trait bounds
PyO3 allows for easy conversion from Rust to Python for certain functions and classes (see the [conversion table](https://pyo3.rs/master/conversions.html)).
PyO3 allows for easy conversion from Rust to Python for certain functions and classes (see the [conversion table](conversions/tables.html).
However, it is not always straightforward to convert Rust code that requires a given trait implementation as an argument.
This tutorial explains how to convert a Rust function that takes a trait as argument for use in Python with classes implementing the same methods as the trait.
@ -488,7 +488,7 @@ pub struct UserModel {
#[pymodule]
fn trait_exposure(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<UserModel>()?;
m.add_wrapped(wrap_pyfunction!(solve_wrapper))?;
m.add_function(wrap_pyfunction!(solve_wrapper))?;
Ok(())
}

View file

@ -61,23 +61,54 @@ such as `getattr`, `setattr`, and `.call`.
**Conversions:**
For a `&PyAny` object reference `any` where the underlying object is a Python-native type such as
a list:
```rust
# use pyo3::prelude::*;
# use pyo3::types::PyList;
# let gil = Python::acquire_gil();
# let py = gil.python();
# use pyo3::{Py, Python, PyAny, PyResult, types::PyList};
# Python::with_gil(|py| -> PyResult<()> {
let obj: &PyAny = PyList::empty(py);
// Convert to &ConcreteType using PyAny::downcast
let _: &PyList = obj.downcast().unwrap();
// To &PyList with PyAny::downcast
let _: &PyList = obj.downcast()?;
// Convert to Py<PyAny> (aka PyObject) using .into()
// To Py<PyAny> (aka PyObject) with .into()
let _: Py<PyAny> = obj.into();
// Convert to Py<ConcreteType> using PyAny::extract
let _: Py<PyList> = obj.extract().unwrap();
// To Py<PyList> with PyAny::extract
let _: Py<PyList> = obj.extract()?;
# Ok(())
# }).unwrap();
```
For a `&PyAny` object reference `any` where the underlying object is a `#[pyclass]`:
```rust
# use pyo3::prelude::*;
# use pyo3::{Py, Python, PyAny, PyResult, types::PyList};
# #[pyclass] #[derive(Clone)] struct MyClass { }
# Python::with_gil(|py| -> PyResult<()> {
let obj: &PyAny = Py::new(py, MyClass { })?.into_ref(py);
// To &PyCell<MyClass> with PyAny::downcast
let _: &PyCell<MyClass> = obj.downcast()?;
// To Py<PyAny> (aka PyObject) with .into()
let _: Py<PyAny> = obj.into();
// To Py<MyClass> with PyAny::extract
let _: Py<MyClass> = obj.extract()?;
// To MyClass with PyAny::extract, if MyClass: Clone
let _: MyClass = obj.extract()?;
// To PyRef<MyClass> or PyRefMut<MyClass> with PyAny::extract
let _: PyRef<MyClass> = obj.extract()?;
let _: PyRefMut<MyClass> = obj.extract()?;
# Ok(())
# }).unwrap();
```
### `PyTuple`, `PyDict`, and many more
@ -99,29 +130,30 @@ To see all Python types exposed by `PyO3` you should consult the
```rust
# use pyo3::prelude::*;
# use pyo3::types::PyList;
# let gil = Python::acquire_gil();
# let py = gil.python();
# Python::with_gil(|py| -> PyResult<()> {
let list = PyList::empty(py);
// Can use methods from PyAny on all Python types due to Deref implementation
let _ = list.repr();
// Use methods from PyAny on all Python types with Deref implementation
let _ = list.repr()?;
// Rust will convert &PyList etc. to &PyAny automatically due to Deref implementation
// To &PyAny automatically with Deref implementation
let _: &PyAny = list;
// For more explicit &PyAny conversion, use .as_ref()
// To &PyAny explicitly with .as_ref()
let _: &PyAny = list.as_ref();
// To convert to Py<T> use .into() or Py::from()
// To Py<T> with .into() or Py::from()
let _: Py<PyList> = list.into();
// To convert to PyObject use .into() or .to_object(py)
// To PyObject with .into() or .to_object(py)
let _: PyObject = list.into();
# Ok(())
# }).unwrap();
```
### `Py<T>`
### `Py<T>` and `PyObject`
**Represents:** a GIL independent reference to a Python object. This can be a Python native type
**Represents:** a GIL-independent reference to a Python object. This can be a Python native type
(like `PyTuple`), or a `pyclass` type implemented in Rust. The most commonly-used variant,
`Py<PyAny>`, is also known as `PyObject`.
@ -133,6 +165,8 @@ Can be cloned using Python reference counts with `.clone()`.
**Conversions:**
For a `Py<PyList>`, the conversions are as below:
```rust
# use pyo3::prelude::*;
# use pyo3::types::PyList;
@ -140,20 +174,47 @@ Can be cloned using Python reference counts with `.clone()`.
# let py = gil.python();
let list: Py<PyList> = PyList::empty(py).into();
// Access to the native type using Py::as_ref(py) or Py::into_ref(py)
// (For #[pyclass] types T, these will return &PyCell<T>)
// Py::as_ref() borrows the object
// To &PyList with Py::as_ref() (borrows from the Py)
let _: &PyList = list.as_ref(py);
# let list_clone = list.clone(); // Just so that the .into() example for PyObject compiles.
// Py::into_ref() moves the reference into pyo3's "object storage"; useful for making APIs
// which return gil-bound references.
# let list_clone = list.clone(); // Because `.into_ref()` will consume `list`.
// To &PyList with Py::into_ref() (moves the pointer into PyO3's object storage)
let _: &PyList = list.into_ref(py);
# let list = list_clone;
// Convert to PyObject with .into()
let _: PyObject = list.into();
// To Py<PyAny> (aka PyObject) with .into()
let _: Py<PyAny> = list.into();
```
For a `#[pyclass] struct MyClass`, the conversions for `Py<MyClass>` are below:
```rust
# use pyo3::prelude::*;
# let gil = Python::acquire_gil();
# let py = gil.python();
# #[pyclass] struct MyClass { }
# Python::with_gil(|py| -> PyResult<()> {
let my_class: Py<MyClass> = Py::new(py, MyClass { })?;
// To &PyCell<MyClass> with Py::as_ref() (borrows from the Py)
let _: &PyCell<MyClass> = my_class.as_ref(py);
# let my_class_clone = my_class.clone(); // Because `.into_ref()` will consume `my_class`.
// To &PyCell<MyClass> with Py::into_ref() (moves the pointer into PyO3's object storage)
let _: &PyCell<MyClass> = my_class.into_ref(py);
# let my_class = my_class_clone.clone();
// To Py<PyAny> (aka PyObject) with .into_py(py)
let _: Py<PyAny> = my_class.into_py(py);
# let my_class = my_class_clone;
// To PyRef<MyClass> with Py::borrow or Py::try_borrow
let _: PyRef<MyClass> = my_class.try_borrow(py)?;
// To PyRefMut<MyClass> with Py::borrow_mut or Py::try_borrow_mut
let _: PyRefMut<MyClass> = my_class.try_borrow_mut(py)?;
# Ok(())
# }).unwrap();
```
### `PyCell<SomeType>`
@ -171,33 +232,46 @@ so it also exposes all of the methods on `PyAny`.
**Conversions:**
`PyCell<T>` can be used to access `&T` and `&mut T` via `PyRef<T>` and `PyRefMut<T>` respectively.
```rust
# use pyo3::prelude::*;
# use pyo3::types::PyList;
# #[pyclass] struct MyClass { }
# let gil = Python::acquire_gil();
# let py = gil.python();
let cell: &PyCell<MyClass> = PyCell::new(py, MyClass { }).unwrap();
# Python::with_gil(|py| -> PyResult<()> {
let cell: &PyCell<MyClass> = PyCell::new(py, MyClass { })?;
// Obtain PyRef<T> with .try_borrow()
let pr: PyRef<MyClass> = cell.try_borrow().unwrap();
# drop(pr);
// To PyRef<T> with .borrow() or .try_borrow()
let py_ref: PyRef<MyClass> = cell.try_borrow()?;
let _: &MyClass = &*py_ref;
# drop(py_ref);
// Obtain PyRefMut<T> with .try_borrow_mut()
let prm: PyRefMut<MyClass> = cell.try_borrow_mut().unwrap();
# drop(prm);
// To PyRefMut<T> with .borrow_mut() or .try_borrow_mut()
let mut py_ref_mut: PyRefMut<MyClass> = cell.try_borrow_mut()?;
let _: &mut MyClass = &mut *py_ref_mut;
# Ok(())
# }).unwrap();
```
// Can use methods from PyAny on PyCell<T> due to Deref implementation
let _ = cell.repr();
`PyCell<T>` can also be accessed like a Python-native type.
// Rust will convert &PyCell<T> to &PyAny automatically due to Deref implementation
```rust
# use pyo3::prelude::*;
# use pyo3::types::PyList;
# #[pyclass] struct MyClass { }
# Python::with_gil(|py| -> PyResult<()> {
let cell: &PyCell<MyClass> = PyCell::new(py, MyClass { })?;
// Use methods from PyAny on PyCell<T> with Deref implementation
let _ = cell.repr()?;
// To &PyAny automatically with Deref implementation
let _: &PyAny = cell;
// For more explicit &PyAny conversion, use .as_ref()
let any: &PyAny = cell.as_ref();
// To obtain a PyCell<T> from PyAny, use PyAny::downcast
let _: &PyCell<MyClass> = any.downcast().unwrap();
// To &PyAny explicitly with .as_ref()
let _: &PyAny = cell.as_ref();
# Ok(())
# }).unwrap();
```
### `PyRef<SomeType>` and `PyRefMut<SomeType>`

View file

@ -17,7 +17,6 @@ pub struct FnArg<'a> {
pub ty: &'a syn::Type,
pub optional: Option<&'a syn::Type>,
pub py: bool,
pub reference: bool,
}
#[derive(Clone, PartialEq, Debug, Copy, Eq)]
@ -214,17 +213,13 @@ impl<'a> FnSpec<'a> {
}
};
let py = crate::utils::if_type_is_python(ty);
let opt = check_ty_optional(ty);
arguments.push(FnArg {
name: ident,
by_ref,
mutability,
ty,
optional: opt,
py,
reference: is_ref(name, ty),
optional: utils::option_type_argument(ty),
py: utils::is_python(ty),
});
}
}
@ -323,55 +318,6 @@ impl<'a> FnSpec<'a> {
}
}
pub fn is_ref(name: &syn::Ident, ty: &syn::Type) -> bool {
match ty {
syn::Type::Reference(_) => return true,
syn::Type::Path(syn::TypePath { ref path, .. }) => {
if let Some(segment) = path.segments.last() {
if "Option" == segment.ident.to_string().as_str() {
match segment.arguments {
syn::PathArguments::AngleBracketed(ref params) => {
if params.args.len() != 1 {
panic!("argument type is not supported by python method: {:?} ({:?}) {:?}",
name,
ty,
path);
}
let last = &params.args[params.args.len() - 1];
if let syn::GenericArgument::Type(syn::Type::Reference(_)) = last {
return true;
}
}
_ => {
panic!(
"argument type is not supported by python method: {:?} ({:?}) {:?}",
name, ty, path
);
}
}
}
}
}
_ => (),
}
false
}
pub(crate) fn check_ty_optional(ty: &syn::Type) -> Option<&syn::Type> {
let path = match ty {
syn::Type::Path(syn::TypePath { ref path, .. }) => path,
_ => return None,
};
let seg = path.segments.last().filter(|s| s.ident == "Option")?;
match seg.arguments {
syn::PathArguments::AngleBracketed(ref params) => match params.args.first() {
Some(syn::GenericArgument::Type(ref ty)) => Some(ty),
_ => None,
},
_ => None,
}
}
#[derive(Clone, PartialEq, Debug)]
struct MethodAttributes {
ty: Option<MethodTypeAttribute>,

View file

@ -2,7 +2,6 @@
//! Code generation for the function that initializes a python module and adds classes and function.
use crate::method;
use crate::pyfunction;
use crate::pyfunction::PyFunctionAttr;
use crate::pymethod;
use crate::pymethod::get_arg_names;
@ -26,8 +25,6 @@ pub fn py_init(fnname: &Ident, name: &Ident, doc: syn::LitStr) -> TokenStream {
const NAME: &'static str = concat!(stringify!(#name), "\0");
static MODULE_DEF: ModuleDef = unsafe { ModuleDef::new(NAME) };
let pool = pyo3::GILPool::new();
let py = pool.python();
pyo3::callback_body!(_py, { MODULE_DEF.make_module(#doc, #fnname) })
}
}
@ -40,14 +37,14 @@ pub fn process_functions_in_module(func: &mut syn::ItemFn) -> syn::Result<()> {
for stmt in func.block.stmts.iter_mut() {
if let syn::Stmt::Item(syn::Item::Fn(ref mut func)) = stmt {
if let Some((module_name, python_name, pyfn_attrs)) =
extract_pyfn_attrs(&mut func.attrs)
extract_pyfn_attrs(&mut func.attrs)?
{
let function_to_python = add_fn_to_module(func, python_name, pyfn_attrs)?;
let function_wrapper_ident = function_wrapper_ident(&func.sig.ident);
let item: syn::ItemFn = syn::parse_quote! {
fn block_wrapper() {
#function_to_python
#module_name.add_wrapped(&#function_wrapper_ident)?;
#module_name.add_function(&#function_wrapper_ident)?;
}
};
stmts.extend(item.block.stmts.into_iter());
@ -61,33 +58,30 @@ pub fn process_functions_in_module(func: &mut syn::ItemFn) -> syn::Result<()> {
}
/// Transforms a rust fn arg parsed with syn into a method::FnArg
fn wrap_fn_argument<'a>(cap: &'a syn::PatType, name: &'a Ident) -> syn::Result<method::FnArg<'a>> {
fn wrap_fn_argument<'a>(cap: &'a syn::PatType) -> syn::Result<method::FnArg<'a>> {
let (mutability, by_ref, ident) = match *cap.pat {
syn::Pat::Ident(ref patid) => (&patid.mutability, &patid.by_ref, &patid.ident),
_ => return Err(syn::Error::new_spanned(&cap.pat, "Unsupported argument")),
};
let py = crate::utils::if_type_is_python(&cap.ty);
let opt = method::check_ty_optional(&cap.ty);
Ok(method::FnArg {
name: ident,
mutability,
by_ref,
ty: &cap.ty,
optional: opt,
py,
reference: method::is_ref(&name, &cap.ty),
optional: utils::option_type_argument(&cap.ty),
py: utils::is_python(&cap.ty),
})
}
/// Extracts the data from the #[pyfn(...)] attribute of a function
fn extract_pyfn_attrs(
attrs: &mut Vec<syn::Attribute>,
) -> Option<(syn::Path, Ident, Vec<pyfunction::Argument>)> {
) -> syn::Result<Option<(syn::Path, Ident, PyFunctionAttr)>> {
let mut new_attrs = Vec::new();
let mut fnname = None;
let mut modname = None;
let mut fn_attrs = Vec::new();
let mut fn_attrs = PyFunctionAttr::default();
for attr in attrs.iter() {
match attr.parse_meta() {
@ -99,23 +93,34 @@ fn extract_pyfn_attrs(
syn::NestedMeta::Meta(syn::Meta::Path(ref path)) => {
modname = Some(path.clone())
}
_ => panic!("The first parameter of pyfn must be a MetaItem"),
_ => {
return Err(syn::Error::new_spanned(
&meta[0],
"The first parameter of pyfn must be a MetaItem",
))
}
}
// read Python function name
match meta[1] {
syn::NestedMeta::Lit(syn::Lit::Str(ref lits)) => {
fnname = Some(syn::Ident::new(&lits.value(), lits.span()));
}
_ => panic!("The second parameter of pyfn must be a Literal"),
_ => {
return Err(syn::Error::new_spanned(
&meta[1],
"The second parameter of pyfn must be a Literal",
))
}
}
// Read additional arguments
if list.nested.len() >= 3 {
fn_attrs = PyFunctionAttr::from_meta(&meta[2..meta.len()])
.unwrap()
.arguments;
fn_attrs = PyFunctionAttr::from_meta(&meta[2..meta.len()])?;
}
} else {
panic!("can not parse 'pyfn' params {:?}", attr);
return Err(syn::Error::new_spanned(
attr,
format!("can not parse 'pyfn' params {:?}", attr),
));
}
}
_ => new_attrs.push(attr.clone()),
@ -123,7 +128,10 @@ fn extract_pyfn_attrs(
}
*attrs = new_attrs;
Some((modname?, fnname?, fn_attrs))
match (modname, fnname) {
(Some(modname), Some(fnname)) => Ok(Some((modname, fnname, fn_attrs))),
_ => Ok(None),
}
}
/// Coordinates the naming of a the add-function-to-python-module function
@ -137,11 +145,11 @@ fn function_wrapper_ident(name: &Ident) -> Ident {
pub fn add_fn_to_module(
func: &mut syn::ItemFn,
python_name: Ident,
pyfn_attrs: Vec<pyfunction::Argument>,
pyfn_attrs: PyFunctionAttr,
) -> syn::Result<TokenStream> {
let mut arguments = Vec::new();
for input in func.sig.inputs.iter() {
for (i, input) in func.sig.inputs.iter().enumerate() {
match input {
syn::FnArg::Receiver(_) => {
return Err(syn::Error::new_spanned(
@ -150,7 +158,27 @@ pub fn add_fn_to_module(
))
}
syn::FnArg::Typed(ref cap) => {
arguments.push(wrap_fn_argument(cap, &func.sig.ident)?);
if pyfn_attrs.pass_module && i == 0 {
if let syn::Type::Reference(tyref) = cap.ty.as_ref() {
if let syn::Type::Path(typath) = tyref.elem.as_ref() {
if typath
.path
.segments
.last()
.map(|seg| seg.ident == "PyModule")
.unwrap_or(false)
{
continue;
}
}
}
return Err(syn::Error::new_spanned(
cap,
"Expected &PyModule as first argument with `pass_module`.",
));
} else {
arguments.push(wrap_fn_argument(cap)?);
}
}
}
}
@ -166,7 +194,7 @@ pub fn add_fn_to_module(
tp: method::FnType::FnStatic,
name: &function_wrapper_ident,
python_name,
attrs: pyfn_attrs,
attrs: pyfn_attrs.arguments,
args: arguments,
output: ty,
doc,
@ -176,10 +204,14 @@ pub fn add_fn_to_module(
let python_name = &spec.python_name;
let wrapper = function_c_wrapper(&func.sig.ident, &spec);
let wrapper = function_c_wrapper(&func.sig.ident, &spec, pyfn_attrs.pass_module);
Ok(quote! {
fn #function_wrapper_ident(py: pyo3::Python) -> pyo3::PyObject {
fn #function_wrapper_ident<'a>(
args: impl Into<pyo3::derive_utils::WrapPyFunctionArguments<'a>>
) -> pyo3::PyResult<pyo3::PyObject> {
let arg = args.into();
let (py, maybe_module) = arg.into_py_and_maybe_module();
#wrapper
let _def = pyo3::class::PyMethodDef {
@ -189,28 +221,49 @@ pub fn add_fn_to_module(
ml_doc: #doc,
};
let (mod_ptr, name) = if let Some(m) = maybe_module {
let mod_ptr = <pyo3::types::PyModule as ::pyo3::conversion::AsPyPointer>::as_ptr(m);
let name = m.name()?;
let name = <&str as pyo3::conversion::IntoPy<PyObject>>::into_py(name, py);
(mod_ptr, <PyObject as pyo3::AsPyPointer>::as_ptr(&name))
} else {
(std::ptr::null_mut(), std::ptr::null_mut())
};
let function = unsafe {
pyo3::PyObject::from_owned_ptr(
py,
pyo3::ffi::PyCFunction_New(
pyo3::ffi::PyCFunction_NewEx(
Box::into_raw(Box::new(_def.as_method_def())),
::std::ptr::null_mut()
mod_ptr,
name
)
)
};
function
Ok(function)
}
})
}
/// Generate static function wrapper (PyCFunction, PyCFunctionWithKeywords)
fn function_c_wrapper(name: &Ident, spec: &method::FnSpec<'_>) -> TokenStream {
fn function_c_wrapper(name: &Ident, spec: &method::FnSpec<'_>, pass_module: bool) -> TokenStream {
let names: Vec<Ident> = get_arg_names(&spec);
let cb = quote! {
let cb;
let slf_module;
if pass_module {
cb = quote! {
#name(_slf, #(#names),*)
};
slf_module = Some(quote! {
let _slf = _py.from_borrowed_ptr::<pyo3::types::PyModule>(_slf);
});
} else {
cb = quote! {
#name(#(#names),*)
};
slf_module = None;
};
let body = pymethod::impl_arg_params(spec, None, cb);
quote! {
@ -221,6 +274,7 @@ fn function_c_wrapper(name: &Ident, spec: &method::FnSpec<'_>) -> TokenStream {
{
const _LOCATION: &'static str = concat!(stringify!(#name), "()");
pyo3::callback_body!(_py, {
#slf_module
let _args = _py.from_borrowed_ptr::<pyo3::types::PyTuple>(_args);
let _kwargs: Option<&pyo3::types::PyDict> = _py.from_borrowed_ptr_or_opt(_kwargs);

View file

@ -73,7 +73,7 @@ pub(crate) fn impl_method_proto(
cls: &syn::Type,
sig: &mut syn::Signature,
meth: &MethodProto,
) -> TokenStream {
) -> syn::Result<TokenStream> {
let ret_ty = match &sig.output {
syn::ReturnType::Default => quote! { () },
syn::ReturnType::Type(_, ty) => {
@ -83,7 +83,7 @@ pub(crate) fn impl_method_proto(
}
};
match *meth {
let toks = match *meth {
MethodProto::Free { proto, .. } => {
let p: syn::Path = syn::parse_str(proto).unwrap();
quote! {
@ -109,7 +109,7 @@ pub(crate) fn impl_method_proto(
let p: syn::Path = syn::parse_str(proto).unwrap();
let slf_name = syn::Ident::new(arg, Span::call_site());
let slf_ty = get_arg_ty(sig, 0);
let slf_ty = get_arg_ty(sig, 0)?;
let tmp: syn::ItemFn = syn::parse_quote! {
fn test(&self) -> <#cls as #p<'p>>::Result {}
};
@ -132,12 +132,12 @@ pub(crate) fn impl_method_proto(
MethodProto::Binary { name, arg, proto } => {
if sig.inputs.len() <= 1 {
println!("Not enough arguments for {}", name);
return TokenStream::new();
return Ok(TokenStream::new());
}
let p: syn::Path = syn::parse_str(proto).unwrap();
let arg_name = syn::Ident::new(arg, Span::call_site());
let arg_ty = get_arg_ty(sig, 1);
let arg_ty = get_arg_ty(sig, 1)?;
let tmp = extract_decl(syn::parse_quote! {
fn test(&self,arg: <#cls as #p<'p>>::#arg_name)-> <#cls as #p<'p>>::Result {}
@ -147,7 +147,7 @@ pub(crate) fn impl_method_proto(
fn test(&self, arg: Option<<#cls as #p<'p>>::#arg_name>) -> <#cls as #p<'p>>::Result {}
});
modify_arg_ty(sig, 1, &tmp, &tmp2);
modify_arg_ty(sig, 1, &tmp, &tmp2)?;
modify_self_ty(sig);
quote! {
@ -165,13 +165,13 @@ pub(crate) fn impl_method_proto(
} => {
if sig.inputs.len() <= 1 {
print_err(format!("Not enough arguments {}", name), quote!(sig));
return TokenStream::new();
return Ok(TokenStream::new());
}
let p: syn::Path = syn::parse_str(proto).unwrap();
let arg1_name = syn::Ident::new(arg1, Span::call_site());
let arg1_ty = get_arg_ty(sig, 0);
let arg1_ty = get_arg_ty(sig, 0)?;
let arg2_name = syn::Ident::new(arg2, Span::call_site());
let arg2_ty = get_arg_ty(sig, 1);
let arg2_ty = get_arg_ty(sig, 1)?;
// rewrite ty
let tmp = extract_decl(syn::parse_quote! {fn test(
@ -182,8 +182,8 @@ pub(crate) fn impl_method_proto(
arg1: Option<<#cls as #p<'p>>::#arg1_name>,
arg2: Option<<#cls as #p<'p>>::#arg2_name>)
-> <#cls as #p<'p>>::Result {}});
modify_arg_ty(sig, 0, &tmp, &tmp2);
modify_arg_ty(sig, 1, &tmp, &tmp2);
modify_arg_ty(sig, 0, &tmp, &tmp2)?;
modify_arg_ty(sig, 1, &tmp, &tmp2)?;
quote! {
impl<'p> #p<'p> for #cls {
@ -201,13 +201,13 @@ pub(crate) fn impl_method_proto(
} => {
if sig.inputs.len() <= 2 {
print_err(format!("Not enough arguments {}", name), quote!(sig));
return TokenStream::new();
return Ok(TokenStream::new());
}
let p: syn::Path = syn::parse_str(proto).unwrap();
let arg1_name = syn::Ident::new(arg1, Span::call_site());
let arg1_ty = get_arg_ty(sig, 1);
let arg1_ty = get_arg_ty(sig, 1)?;
let arg2_name = syn::Ident::new(arg2, Span::call_site());
let arg2_ty = get_arg_ty(sig, 2);
let arg2_ty = get_arg_ty(sig, 2)?;
// rewrite ty
let tmp = extract_decl(syn::parse_quote! {fn test(
@ -220,8 +220,8 @@ pub(crate) fn impl_method_proto(
arg1: Option<<#cls as #p<'p>>::#arg1_name>,
arg2: Option<<#cls as #p<'p>>::#arg2_name>)
-> <#cls as #p<'p>>::Result {}});
modify_arg_ty(sig, 1, &tmp, &tmp2);
modify_arg_ty(sig, 2, &tmp, &tmp2);
modify_arg_ty(sig, 1, &tmp, &tmp2)?;
modify_arg_ty(sig, 2, &tmp, &tmp2)?;
modify_self_ty(sig);
quote! {
@ -241,15 +241,15 @@ pub(crate) fn impl_method_proto(
} => {
if sig.inputs.len() <= 2 {
print_err(format!("Not enough arguments {}", name), quote!(sig));
return TokenStream::new();
return Ok(TokenStream::new());
}
let p: syn::Path = syn::parse_str(proto).unwrap();
let arg1_name = syn::Ident::new(arg1, Span::call_site());
let arg1_ty = get_arg_ty(sig, 0);
let arg1_ty = get_arg_ty(sig, 0)?;
let arg2_name = syn::Ident::new(arg2, Span::call_site());
let arg2_ty = get_arg_ty(sig, 1);
let arg2_ty = get_arg_ty(sig, 1)?;
let arg3_name = syn::Ident::new(arg3, Span::call_site());
let arg3_ty = get_arg_ty(sig, 2);
let arg3_ty = get_arg_ty(sig, 2)?;
// rewrite ty
let tmp = extract_decl(syn::parse_quote! {fn test(
@ -262,9 +262,9 @@ pub(crate) fn impl_method_proto(
arg2: Option<<#cls as #p<'p>>::#arg2_name>,
arg3: Option<<#cls as #p<'p>>::#arg3_name>)
-> <#cls as #p<'p>>::Result {}});
modify_arg_ty(sig, 0, &tmp, &tmp2);
modify_arg_ty(sig, 1, &tmp, &tmp2);
modify_arg_ty(sig, 2, &tmp, &tmp2);
modify_arg_ty(sig, 0, &tmp, &tmp2)?;
modify_arg_ty(sig, 1, &tmp, &tmp2)?;
modify_arg_ty(sig, 2, &tmp, &tmp2)?;
quote! {
impl<'p> #p<'p> for #cls {
@ -284,15 +284,15 @@ pub(crate) fn impl_method_proto(
} => {
if sig.inputs.len() <= 3 {
print_err(format!("Not enough arguments {}", name), quote!(sig));
return TokenStream::new();
return Ok(TokenStream::new());
}
let p: syn::Path = syn::parse_str(proto).unwrap();
let arg1_name = syn::Ident::new(arg1, Span::call_site());
let arg1_ty = get_arg_ty(sig, 1);
let arg1_ty = get_arg_ty(sig, 1)?;
let arg2_name = syn::Ident::new(arg2, Span::call_site());
let arg2_ty = get_arg_ty(sig, 2);
let arg2_ty = get_arg_ty(sig, 2)?;
let arg3_name = syn::Ident::new(arg3, Span::call_site());
let arg3_ty = get_arg_ty(sig, 3);
let arg3_ty = get_arg_ty(sig, 3)?;
// rewrite ty
let tmp = extract_decl(syn::parse_quote! {fn test(
@ -307,9 +307,9 @@ pub(crate) fn impl_method_proto(
arg2: Option<<#cls as #p<'p>>::#arg2_name>,
arg3: Option<<#cls as #p<'p>>::#arg3_name>)
-> <#cls as #p<'p>>::Result {}});
modify_arg_ty(sig, 1, &tmp, &tmp2);
modify_arg_ty(sig, 2, &tmp, &tmp2);
modify_arg_ty(sig, 3, &tmp, &tmp2);
modify_arg_ty(sig, 1, &tmp, &tmp2)?;
modify_arg_ty(sig, 2, &tmp, &tmp2)?;
modify_arg_ty(sig, 3, &tmp, &tmp2)?;
modify_self_ty(sig);
quote! {
@ -321,11 +321,12 @@ pub(crate) fn impl_method_proto(
}
}
}
}
};
Ok(toks)
}
/// Some hacks for arguments: get `T` from `Option<T>` and insert lifetime
fn get_arg_ty(sig: &syn::Signature, idx: usize) -> syn::Type {
fn get_arg_ty(sig: &syn::Signature, idx: usize) -> syn::Result<syn::Type> {
fn get_option_ty(path: &syn::Path) -> Option<syn::Type> {
let seg = path.segments.last()?;
if seg.ident == "Option" {
@ -344,10 +345,15 @@ fn get_arg_ty(sig: &syn::Signature, idx: usize) -> syn::Type {
syn::Type::Path(ref ty) => get_option_ty(&ty.path).unwrap_or_else(|| *cap.ty.clone()),
_ => *cap.ty.clone(),
},
ty => panic!("Unsupported argument type: {:?}", ty),
ty => {
return Err(syn::Error::new_spanned(
ty,
format!("Unsupported argument type: {:?}", ty),
))
}
};
insert_lifetime(&mut ty);
ty
Ok(ty)
}
/// Insert lifetime `'p` to `PyRef<Self>` or references (e.g., `&PyType`).
@ -395,26 +401,27 @@ fn modify_arg_ty(
idx: usize,
decl1: &syn::Signature,
decl2: &syn::Signature,
) {
) -> syn::Result<()> {
let arg = sig.inputs[idx].clone();
match arg {
syn::FnArg::Typed(ref cap) => match *cap.ty {
syn::Type::Path(ref typath) => {
let seg = typath.path.segments.last().unwrap().clone();
if seg.ident == "Option" {
sig.inputs[idx] = fix_name(&cap.pat, &decl2.inputs[idx]);
sig.inputs[idx] = fix_name(&cap.pat, &decl2.inputs[idx])?;
} else {
sig.inputs[idx] = fix_name(&cap.pat, &decl1.inputs[idx]);
sig.inputs[idx] = fix_name(&cap.pat, &decl1.inputs[idx])?;
}
}
_ => {
sig.inputs[idx] = fix_name(&cap.pat, &decl1.inputs[idx]);
sig.inputs[idx] = fix_name(&cap.pat, &decl1.inputs[idx])?;
}
},
_ => panic!("not supported"),
_ => return Err(syn::Error::new_spanned(arg, "not supported")),
}
sig.output = decl1.output.clone();
Ok(())
}
fn modify_self_ty(sig: &mut syn::Signature) {
@ -426,15 +433,15 @@ fn modify_self_ty(sig: &mut syn::Signature) {
}
}
fn fix_name(pat: &syn::Pat, arg: &syn::FnArg) -> syn::FnArg {
fn fix_name(pat: &syn::Pat, arg: &syn::FnArg) -> syn::Result<syn::FnArg> {
if let syn::FnArg::Typed(ref cap) = arg {
syn::FnArg::Typed(syn::PatType {
Ok(syn::FnArg::Typed(syn::PatType {
attrs: cap.attrs.clone(),
pat: Box::new(pat.clone()),
colon_token: cap.colon_token,
ty: cap.ty.clone(),
})
}))
} else {
panic!("func.rs::296")
Err(syn::Error::new_spanned(arg, "Expected a typed argument"))
}
}

View file

@ -24,6 +24,7 @@ pub struct PyFunctionAttr {
has_kw: bool,
has_varargs: bool,
has_kwargs: bool,
pub pass_module: bool,
}
impl syn::parse::Parse for PyFunctionAttr {
@ -45,6 +46,9 @@ impl PyFunctionAttr {
pub fn add_item(&mut self, item: &NestedMeta) -> syn::Result<()> {
match item {
NestedMeta::Meta(syn::Meta::Path(ref ident)) if ident.is_ident("pass_module") => {
self.pass_module = true;
}
NestedMeta::Meta(syn::Meta::Path(ref ident)) => self.add_work(item, ident)?,
NestedMeta::Meta(syn::Meta::NameValue(ref nv)) => {
self.add_name_value(item, nv)?;
@ -204,7 +208,7 @@ pub fn parse_name_attribute(attrs: &mut Vec<syn::Attribute>) -> syn::Result<Opti
pub fn build_py_function(ast: &mut syn::ItemFn, args: PyFunctionAttr) -> syn::Result<TokenStream> {
let python_name =
parse_name_attribute(&mut ast.attrs)?.unwrap_or_else(|| ast.sig.ident.unraw());
add_fn_to_module(ast, python_name, args.arguments)
add_fn_to_module(ast, python_name, args)
}
#[cfg(test)]

View file

@ -488,57 +488,47 @@ fn impl_arg_param(
let arg_value = quote!(output[#option_pos]);
*option_pos += 1;
return if let Some(ty) = arg.optional.as_ref() {
let default = if let Some(d) = spec.default_value(name).filter(|d| d.to_string() != "None")
{
quote! { Some(#d) }
} else {
quote! { None }
let default = match (spec.default_value(name), arg.optional.is_some()) {
(Some(default), true) if default.to_string() != "None" => quote! { Some(#default) },
(Some(default), _) => quote! { #default },
(None, true) => quote! { None },
(None, false) => quote! { panic!("Failed to extract required method argument") },
};
if let syn::Type::Reference(tref) = ty {
return if let syn::Type::Reference(tref) = arg.optional.as_ref().unwrap_or(&ty) {
let (tref, mut_) = preprocess_tref(tref, self_);
let (target_ty, borrow_tmp) = if arg.optional.is_some() {
// Get Option<&T> from Option<PyRef<T>>
(
quote! { Option<<#tref as pyo3::derive_utils::ExtractExt>::Target> },
// To support Rustc 1.39.0, we don't use as_deref here...
let tmp_as_deref = if mut_.is_some() {
if mut_.is_some() {
quote! { _tmp.as_mut().map(std::ops::DerefMut::deref_mut) }
} else {
quote! { _tmp.as_ref().map(std::ops::Deref::deref) }
};
// Get Option<&T> from Option<PyRef<T>>
quote! {
let #mut_ _tmp = match #arg_value {
Some(_obj) => {
_obj.extract::<Option<<#tref as pyo3::derive_utils::ExtractExt>::Target>>()?
},
None => #default,
};
let #arg_name = #tmp_as_deref;
}
)
} else {
quote! {
let #arg_name = match #arg_value {
Some(_obj) => _obj.extract()?,
None => #default,
};
}
}
} else if let Some(default) = spec.default_value(name) {
quote! {
let #arg_name = match #arg_value {
Some(_obj) => _obj.extract()?,
None => #default,
};
}
} else if let syn::Type::Reference(tref) = arg.ty {
let (tref, mut_) = preprocess_tref(tref, self_);
// Get &T from PyRef<T>
(
quote! { <#tref as pyo3::derive_utils::ExtractExt>::Target },
quote! { &#mut_ *_tmp },
)
};
quote! {
let #mut_ _tmp: <#tref as pyo3::derive_utils::ExtractExt>::Target
= #arg_value.unwrap().extract()?;
let #arg_name = &#mut_ *_tmp;
let #mut_ _tmp: #target_ty = match #arg_value {
Some(_obj) => _obj.extract()?,
None => #default,
};
let #arg_name = #borrow_tmp;
}
} else {
quote! {
let #arg_name = #arg_value.unwrap().extract()?;
let #arg_name = match #arg_value {
Some(_obj) => _obj.extract()?,
None => #default,
};
}
};
@ -739,7 +729,11 @@ pub(crate) fn impl_py_getter_def(
/// Split an argument of pyo3::Python from the front of the arg list, if present
fn split_off_python_arg<'a>(args: &'a [FnArg<'a>]) -> (Option<&FnArg>, &[FnArg]) {
if args.get(0).map(|py| utils::if_type_is_python(&py.ty)) == Some(true) {
if args
.get(0)
.map(|py| utils::is_python(&py.ty))
.unwrap_or(false)
{
(Some(&args[0]), &args[1..])
} else {
(None, args)

View file

@ -67,7 +67,7 @@ fn impl_proto_impl(
if let syn::ImplItem::Method(ref mut met) = iimpl {
// impl Py~Protocol<'p> { type = ... }
if let Some(m) = proto.get_proto(&met.sig.ident) {
impl_method_proto(ty, &mut met.sig, m).to_tokens(&mut trait_impls);
impl_method_proto(ty, &mut met.sig, m)?.to_tokens(&mut trait_impls);
// Insert the method to the HashSet
method_names.insert(met.sig.ident.to_string());
}
@ -152,6 +152,7 @@ fn slot_initialization(
Span::call_site(),
);
Ok(quote! {
#[allow(non_snake_case)]
#[pyo3::ctor::ctor]
fn #init() {
let mut table = #table::default();

View file

@ -8,7 +8,7 @@ pub fn print_err(msg: String, t: TokenStream) {
}
/// Check if the given type `ty` is `pyo3::Python`.
pub fn if_type_is_python(ty: &syn::Type) -> bool {
pub fn is_python(ty: &syn::Type) -> bool {
match ty {
syn::Type::Path(ref typath) => typath
.path
@ -20,6 +20,19 @@ pub fn if_type_is_python(ty: &syn::Type) -> bool {
}
}
/// If `ty` is Option<T>, return `Some(T)`, else None.
pub fn option_type_argument(ty: &syn::Type) -> Option<&syn::Type> {
if let syn::Type::Path(syn::TypePath { path, .. }) = ty {
let seg = path.segments.last().filter(|s| s.ident == "Option")?;
if let syn::PathArguments::AngleBracketed(params) = &seg.arguments {
if let syn::GenericArgument::Type(ty) = params.args.first()? {
return Some(ty);
}
}
}
None
}
pub fn is_text_signature_attr(attr: &syn::Attribute) -> bool {
attr.path.is_ident("text_signature")
}

View file

@ -207,3 +207,34 @@ where
<R as std::convert::TryFrom<&'a PyCell<T>>>::try_from(cell)
}
}
/// Enum to abstract over the arguments of Python function wrappers.
#[doc(hidden)]
pub enum WrapPyFunctionArguments<'a> {
Python(Python<'a>),
PyModule(&'a PyModule),
}
impl<'a> WrapPyFunctionArguments<'a> {
pub fn into_py_and_maybe_module(self) -> (Python<'a>, Option<&'a PyModule>) {
match self {
WrapPyFunctionArguments::Python(py) => (py, None),
WrapPyFunctionArguments::PyModule(module) => {
let py = module.py();
(py, Some(module))
}
}
}
}
impl<'a> From<Python<'a>> for WrapPyFunctionArguments<'a> {
fn from(py: Python<'a>) -> WrapPyFunctionArguments<'a> {
WrapPyFunctionArguments::Python(py)
}
}
impl<'a> From<&'a PyModule> for WrapPyFunctionArguments<'a> {
fn from(module: &'a PyModule) -> WrapPyFunctionArguments<'a> {
WrapPyFunctionArguments::PyModule(module)
}
}

View file

@ -63,6 +63,7 @@ pub unsafe fn PyImport_ImportModuleEx(
extern "C" {
pub fn PyImport_GetImporter(path: *mut PyObject) -> *mut PyObject;
#[cfg_attr(PyPy, link_name = "PyPyImport_Import")]
pub fn PyImport_Import(name: *mut PyObject) -> *mut PyObject;
#[cfg_attr(PyPy, link_name = "PyPyImport_ReloadModule")]
pub fn PyImport_ReloadModule(m: *mut PyObject) -> *mut PyObject;

View file

@ -1,6 +1,6 @@
use crate::ffi::object::{PyObject, PyTypeObject, Py_TYPE};
use std::mem;
use std::os::raw::{c_char, c_int};
use std::{mem, ptr};
#[cfg_attr(windows, link(name = "pythonXY"))]
extern "C" {
@ -96,19 +96,16 @@ impl Default for PyMethodDef {
}
}
#[inline]
pub unsafe fn PyCFunction_New(ml: *mut PyMethodDef, slf: *mut PyObject) -> *mut PyObject {
#[cfg_attr(PyPy, link_name = "PyPyCFunction_NewEx")]
PyCFunction_NewEx(ml, slf, ptr::null_mut())
}
extern "C" {
#[cfg_attr(PyPy, link_name = "PyPyCFunction_NewEx")]
pub fn PyCFunction_NewEx(
arg1: *mut PyMethodDef,
arg2: *mut PyObject,
arg3: *mut PyObject,
ml: *mut PyMethodDef,
slf: *mut PyObject,
module: *mut PyObject,
) -> *mut PyObject;
#[cfg_attr(PyPy, link_name = "PyPyCFunction_NewEx")]
pub fn PyCFunction_New(ml: *mut PyMethodDef, slf: *mut PyObject) -> *mut PyObject;
}
/* Flag passed to newmethodobject */

View file

@ -25,6 +25,7 @@ extern "C" {
pub fn PyModule_New(name: *const c_char) -> *mut PyObject;
#[cfg_attr(PyPy, link_name = "PyPyModule_GetDict")]
pub fn PyModule_GetDict(arg1: *mut PyObject) -> *mut PyObject;
#[cfg(not(PyPy))]
pub fn PyModule_GetNameObject(arg1: *mut PyObject) -> *mut PyObject;
#[cfg_attr(PyPy, link_name = "PyPyModule_GetName")]
pub fn PyModule_GetName(arg1: *mut PyObject) -> *const c_char;

View file

@ -71,7 +71,7 @@
//! #[pymodule]
//! /// A Python module implemented in Rust.
//! fn string_sum(py: Python, m: &PyModule) -> PyResult<()> {
//! m.add_wrapped(wrap_pyfunction!(sum_as_string))?;
//! m.add_function(wrap_pyfunction!(sum_as_string))?;
//!
//! Ok(())
//! }

View file

@ -134,7 +134,7 @@ impl<'p> Python<'p> {
/// let gil = Python::acquire_gil();
/// let py = gil.python();
/// let m = PyModule::new(py, "pcount").unwrap();
/// m.add_wrapped(wrap_pyfunction!(parallel_count)).unwrap();
/// m.add_function(wrap_pyfunction!(parallel_count)).unwrap();
/// let locals = [("pcount", m)].into_py_dict(py);
/// py.run(r#"
/// s = ["Flow", "my", "tears", "the", "Policeman", "Said"]
@ -280,13 +280,11 @@ impl<'p> Python<'p> {
.unwrap_or_else(|| ffi::PyModule_GetDict(mptr));
let locals = locals.map(AsPyPointer::as_ptr).unwrap_or(globals);
let res_ptr = ffi::PyRun_StringFlags(
code.as_ptr(),
start,
globals,
locals,
::std::ptr::null_mut(),
);
let code_obj = ffi::Py_CompileString(code.as_ptr(), "<string>\0".as_ptr() as _, start);
if code_obj.is_null() {
return Err(PyErr::fetch(self));
}
let res_ptr = ffi::PyEval_EvalCode(code_obj, globals, locals);
self.from_owned_ptr_or_err(res_ptr)
}

View file

@ -224,17 +224,16 @@ fn initialize_tp_dict(
type_object: *mut ffi::PyObject,
items: Vec<(&'static str, PyObject)>,
) -> PyResult<()> {
use std::ffi::CString;
// We hold the GIL: the dictionary update can be considered atomic from
// the POV of other threads.
for (key, val) in items {
let ret = unsafe {
ffi::PyObject_SetAttrString(type_object, CString::new(key)?.as_ptr(), val.into_ptr())
};
crate::types::with_tmp_string(py, key, |key| {
let ret = unsafe { ffi::PyObject_SetAttr(type_object, key, val.into_ptr()) };
if ret < 0 {
return Err(PyErr::fetch(py));
}
Ok(())
})?;
}
Ok(())
}

View file

@ -81,6 +81,7 @@ impl<'source> FromPyObject<'source> for f32 {
#[cfg(test)]
mod test {
#[cfg(not(Py_LIMITED_API))]
use crate::ffi::PyFloat_AS_DOUBLE;
use crate::{AsPyPointer, Python, ToPyObject};
@ -103,6 +104,7 @@ mod test {
num_to_py_object_and_back!(to_from_f32, f32, f32);
num_to_py_object_and_back!(int_to_float, i32, f64);
#[cfg(not(Py_LIMITED_API))]
#[test]
fn test_as_double_macro() {
use assert_approx_eq::assert_approx_eq;

View file

@ -21,6 +21,7 @@ pub use self::num::PyLong as PyInt;
pub use self::sequence::PySequence;
pub use self::set::{PyFrozenSet, PySet};
pub use self::slice::{PySlice, PySliceIndices};
pub(crate) use self::string::with_tmp_string;
pub use self::string::{PyString, PyString as PyUnicode};
pub use self::tuple::PyTuple;
pub use self::typeobject::PyType;

View file

@ -2,6 +2,7 @@
//
// based on Daniel Grunwald's https://github.com/dgrunwald/rust-cpython
use crate::callback::IntoPyCallbackOutput;
use crate::err::{PyErr, PyResult};
use crate::exceptions;
use crate::ffi;
@ -24,14 +25,16 @@ pyobject_native_var_type!(PyModule, ffi::PyModule_Type, ffi::PyModule_Check);
impl PyModule {
/// Creates a new module object with the `__name__` attribute set to name.
pub fn new<'p>(py: Python<'p>, name: &str) -> PyResult<&'p PyModule> {
// Could use PyModule_NewObject, but it doesn't exist on PyPy.
let name = CString::new(name)?;
unsafe { py.from_owned_ptr_or_err(ffi::PyModule_New(name.as_ptr())) }
}
/// Imports the Python module with the specified name.
pub fn import<'p>(py: Python<'p>, name: &str) -> PyResult<&'p PyModule> {
let name = CString::new(name)?;
unsafe { py.from_owned_ptr_or_err(ffi::PyImport_ImportModule(name.as_ptr())) }
crate::types::with_tmp_string(py, name, |name| unsafe {
py.from_owned_ptr_or_err(ffi::PyImport_Import(name))
})
}
/// Loads the Python code specified into a new module.
@ -184,21 +187,102 @@ impl PyModule {
/// Use this together with the`#[pyfunction]` and [wrap_pyfunction!] or `#[pymodule]` and
/// [wrap_pymodule!].
///
/// ```rust,ignore
/// m.add_wrapped(wrap_pyfunction!(double));
/// m.add_wrapped(wrap_pymodule!(utils));
/// ```rust
/// use pyo3::prelude::*;
/// #[pymodule]
/// fn utils(_py: Python, _module: &PyModule) -> PyResult<()> {
/// Ok(())
/// }
///
/// #[pyfunction]
/// fn double(x: usize) -> usize {
/// x * 2
/// }
/// #[pymodule]
/// fn top_level(_py: Python, module: &PyModule) -> PyResult<()> {
/// module.add_wrapped(pyo3::wrap_pymodule!(utils))?;
/// module.add_wrapped(pyo3::wrap_pyfunction!(double))
/// }
/// ```
///
/// You can also add a function with a custom name using [add](PyModule::add):
///
/// ```rust,ignore
/// m.add("also_double", wrap_pyfunction!(double)(py));
/// m.add("also_double", wrap_pyfunction!(double)(m)?)?;
/// ```
pub fn add_wrapped(&self, wrapper: &impl Fn(Python) -> PyObject) -> PyResult<()> {
let function = wrapper(self.py());
let name = function
.getattr(self.py(), "__name__")
.expect("A function or module must have a __name__");
self.add(name.extract(self.py()).unwrap(), function)
///
/// **This function will be deprecated in the next release. Please use the specific
/// [add_function] and [add_submodule] functions instead.**
pub fn add_wrapped<'a, T>(&'a self, wrapper: &impl Fn(Python<'a>) -> T) -> PyResult<()>
where
T: IntoPyCallbackOutput<PyObject>,
{
let py = self.py();
let function = wrapper(py).convert(py)?;
let name = function.getattr(py, "__name__")?;
let name = name.extract(py)?;
self.add(name, function)
}
/// Add a submodule to a module.
///
/// Use this together with `#[pymodule]` and [wrap_pymodule!].
///
/// ```rust
/// use pyo3::prelude::*;
///
/// fn init_utils(module: &PyModule) -> PyResult<()> {
/// module.add("super_useful_constant", "important")
/// }
/// #[pymodule]
/// fn top_level(py: Python, module: &PyModule) -> PyResult<()> {
/// let utils = PyModule::new(py, "utils")?;
/// init_utils(utils)?;
/// module.add_submodule(utils)
/// }
/// ```
pub fn add_submodule(&self, module: &PyModule) -> PyResult<()> {
let name = module.name()?;
self.add(name, module)
}
/// Add a function to a module.
///
/// Use this together with the`#[pyfunction]` and [wrap_pyfunction!].
///
/// ```rust
/// use pyo3::prelude::*;
/// #[pyfunction]
/// fn double(x: usize) -> usize {
/// x * 2
/// }
/// #[pymodule]
/// fn double_mod(_py: Python, module: &PyModule) -> PyResult<()> {
/// module.add_function(pyo3::wrap_pyfunction!(double))
/// }
/// ```
///
/// You can also add a function with a custom name using [add](PyModule::add):
///
/// ```rust
/// use pyo3::prelude::*;
/// #[pyfunction]
/// fn double(x: usize) -> usize {
/// x * 2
/// }
/// #[pymodule]
/// fn double_mod(_py: Python, module: &PyModule) -> PyResult<()> {
/// module.add("also_double", pyo3::wrap_pyfunction!(double)(module)?)
/// }
/// ```
pub fn add_function<'a>(
&'a self,
wrapper: &impl Fn(&'a Self) -> PyResult<PyObject>,
) -> PyResult<()> {
let py = self.py();
let function = wrapper(self)?;
let name = function.getattr(py, "__name__")?;
let name = name.extract(py)?;
self.add(name, function)
}
}

View file

@ -78,6 +78,24 @@ impl PyString {
}
}
/// Convenience for calling Python APIs with a temporary string
/// object created from a given Rust string.
pub(crate) fn with_tmp_string<F, R>(py: Python, s: &str, f: F) -> PyResult<R>
where
F: FnOnce(*mut ffi::PyObject) -> PyResult<R>,
{
unsafe {
let s_obj =
ffi::PyUnicode_FromStringAndSize(s.as_ptr() as *const _, s.len() as ffi::Py_ssize_t);
if s_obj.is_null() {
return Err(PyErr::fetch(py));
}
let ret = f(s_obj);
ffi::Py_DECREF(s_obj);
ret
}
}
/// Converts a Rust `str` to a Python object.
/// See `PyString::new` for details on the conversion.
impl ToPyObject for str {

View file

@ -14,7 +14,7 @@ fn test_pybytes_bytes_conversion() {
let gil = Python::acquire_gil();
let py = gil.python();
let f = wrap_pyfunction!(bytes_pybytes_conversion)(py);
let f = wrap_pyfunction!(bytes_pybytes_conversion)(py).unwrap();
py_assert!(py, f, "f(b'Hello World') == b'Hello World'");
}
@ -28,7 +28,7 @@ fn test_pybytes_vec_conversion() {
let gil = Python::acquire_gil();
let py = gil.python();
let f = wrap_pyfunction!(bytes_vec_conversion)(py);
let f = wrap_pyfunction!(bytes_vec_conversion)(py).unwrap();
py_assert!(py, f, "f(b'Hello World') == b'Hello World'");
}
@ -37,6 +37,6 @@ fn test_bytearray_vec_conversion() {
let gil = Python::acquire_gil();
let py = gil.python();
let f = wrap_pyfunction!(bytes_vec_conversion)(py);
let f = wrap_pyfunction!(bytes_vec_conversion)(py).unwrap();
py_assert!(py, f, "f(bytearray(b'Hello World')) == b'Hello World'");
}

View file

@ -4,6 +4,7 @@ fn test_compile_errors() {
let t = trybuild::TestCases::new();
t.compile_fail("tests/ui/invalid_frompy_derive.rs");
t.compile_fail("tests/ui/invalid_macro_args.rs");
t.compile_fail("tests/ui/invalid_need_module_arg_position.rs");
t.compile_fail("tests/ui/invalid_property_args.rs");
t.compile_fail("tests/ui/invalid_pyclass_args.rs");
t.compile_fail("tests/ui/invalid_pymethod_names.rs");

View file

@ -19,7 +19,7 @@ fn fail_to_open_file() -> PyResult<()> {
fn test_filenotfounderror() {
let gil = Python::acquire_gil();
let py = gil.python();
let fail_to_open_file = wrap_pyfunction!(fail_to_open_file)(py);
let fail_to_open_file = wrap_pyfunction!(fail_to_open_file)(py).unwrap();
py_run!(
py,
@ -64,7 +64,7 @@ fn call_fail_with_custom_error() -> PyResult<()> {
fn test_custom_error() {
let gil = Python::acquire_gil();
let py = gil.python();
let call_fail_with_custom_error = wrap_pyfunction!(call_fail_with_custom_error)(py);
let call_fail_with_custom_error = wrap_pyfunction!(call_fail_with_custom_error)(py).unwrap();
py_run!(
py,

View file

@ -1,6 +1,6 @@
use pyo3::prelude::*;
use pyo3::types::{IntoPyDict, PyTuple};
use pyo3::types::{IntoPyDict, PyDict, PyTuple};
mod common;
@ -35,7 +35,7 @@ fn double(x: usize) -> usize {
/// This module is implemented in Rust.
#[pymodule]
fn module_with_functions(py: Python, m: &PyModule) -> PyResult<()> {
fn module_with_functions(_py: Python, m: &PyModule) -> PyResult<()> {
use pyo3::wrap_pyfunction;
#[pyfn(m, "sum_as_string")]
@ -49,6 +49,11 @@ fn module_with_functions(py: Python, m: &PyModule) -> PyResult<()> {
Ok(42)
}
#[pyfn(m, "with_module", pass_module)]
fn with_module(module: &PyModule) -> PyResult<&str> {
module.name()
}
#[pyfn(m, "double_value")]
fn double_value(v: &ValueClass) -> usize {
v.value * 2
@ -60,8 +65,8 @@ fn module_with_functions(py: Python, m: &PyModule) -> PyResult<()> {
m.add("foo", "bar").unwrap();
m.add_wrapped(wrap_pyfunction!(double)).unwrap();
m.add("also_double", wrap_pyfunction!(double)(py)).unwrap();
m.add_function(wrap_pyfunction!(double)).unwrap();
m.add("also_double", wrap_pyfunction!(double)(m)?).unwrap();
Ok(())
}
@ -97,6 +102,7 @@ fn test_module_with_functions() {
run("assert module_with_functions.also_double(3) == 6");
run("assert module_with_functions.also_double.__doc__ == 'Doubles the given value'");
run("assert module_with_functions.double_value(module_with_functions.ValueClass(1)) == 2");
run("assert module_with_functions.with_module() == 'module_with_functions'");
}
#[pymodule(other_name)]
@ -157,7 +163,7 @@ fn r#move() -> usize {
fn raw_ident_module(_py: Python, module: &PyModule) -> PyResult<()> {
use pyo3::wrap_pyfunction;
module.add_wrapped(wrap_pyfunction!(r#move))
module.add_function(wrap_pyfunction!(r#move))
}
#[test]
@ -182,7 +188,7 @@ fn custom_named_fn() -> usize {
fn foobar_module(_py: Python, m: &PyModule) -> PyResult<()> {
use pyo3::wrap_pyfunction;
m.add_wrapped(wrap_pyfunction!(custom_named_fn))?;
m.add_function(wrap_pyfunction!(custom_named_fn))?;
m.dict().set_item("yay", "me")?;
Ok(())
}
@ -212,11 +218,18 @@ fn subfunction() -> String {
"Subfunction".to_string()
}
#[pymodule]
fn submodule(_py: Python, module: &PyModule) -> PyResult<()> {
fn submodule(module: &PyModule) -> PyResult<()> {
use pyo3::wrap_pyfunction;
module.add_wrapped(wrap_pyfunction!(subfunction))?;
module.add_function(wrap_pyfunction!(subfunction))?;
Ok(())
}
#[pymodule]
fn submodule_with_init_fn(_py: Python, module: &PyModule) -> PyResult<()> {
use pyo3::wrap_pyfunction;
module.add_function(wrap_pyfunction!(subfunction))?;
Ok(())
}
@ -226,11 +239,16 @@ fn superfunction() -> String {
}
#[pymodule]
fn supermodule(_py: Python, module: &PyModule) -> PyResult<()> {
use pyo3::{wrap_pyfunction, wrap_pymodule};
fn supermodule(py: Python, module: &PyModule) -> PyResult<()> {
use pyo3::wrap_pyfunction;
module.add_wrapped(wrap_pyfunction!(superfunction))?;
module.add_wrapped(wrap_pymodule!(submodule))?;
module.add_function(wrap_pyfunction!(superfunction))?;
let module_to_add = PyModule::new(py, "submodule")?;
submodule(module_to_add)?;
module.add_submodule(module_to_add)?;
let module_to_add = PyModule::new(py, "submodule_with_init_fn")?;
submodule_with_init_fn(py, module_to_add)?;
module.add_submodule(module_to_add)?;
Ok(())
}
@ -252,6 +270,11 @@ fn test_module_nesting() {
supermodule,
"supermodule.submodule.subfunction() == 'Subfunction'"
);
py_assert!(
py,
supermodule,
"supermodule.submodule_with_init_fn.subfunction() == 'Subfunction'"
);
}
// Test that argument parsing specification works for pyfunctions
@ -268,7 +291,7 @@ fn vararg_module(_py: Python, m: &PyModule) -> PyResult<()> {
ext_vararg_fn(py, a, vararg)
}
m.add_wrapped(pyo3::wrap_pyfunction!(ext_vararg_fn))
m.add_function(pyo3::wrap_pyfunction!(ext_vararg_fn))
.unwrap();
Ok(())
}
@ -305,3 +328,82 @@ fn test_module_with_constant() {
py_assert!(py, m, "isinstance(m.ANON, m.AnonClass)");
});
}
#[pyfunction(pass_module)]
fn pyfunction_with_module(module: &PyModule) -> PyResult<&str> {
module.name()
}
#[pyfunction(pass_module)]
fn pyfunction_with_module_and_py<'a>(
module: &'a PyModule,
_python: Python<'a>,
) -> PyResult<&'a str> {
module.name()
}
#[pyfunction(pass_module)]
fn pyfunction_with_module_and_arg(module: &PyModule, string: String) -> PyResult<(&str, String)> {
module.name().map(|s| (s, string))
}
#[pyfunction(pass_module, string = "\"foo\"")]
fn pyfunction_with_module_and_default_arg<'a>(
module: &'a PyModule,
string: &str,
) -> PyResult<(&'a str, String)> {
module.name().map(|s| (s, string.into()))
}
#[pyfunction(pass_module, args = "*", kwargs = "**")]
fn pyfunction_with_module_and_args_kwargs<'a>(
module: &'a PyModule,
args: &PyTuple,
kwargs: Option<&PyDict>,
) -> PyResult<(&'a str, usize, Option<usize>)> {
module
.name()
.map(|s| (s, args.len(), kwargs.map(|d| d.len())))
}
#[pymodule]
fn module_with_functions_with_module(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(pyo3::wrap_pyfunction!(pyfunction_with_module))?;
m.add_function(pyo3::wrap_pyfunction!(pyfunction_with_module_and_py))?;
m.add_function(pyo3::wrap_pyfunction!(pyfunction_with_module_and_arg))?;
m.add_function(pyo3::wrap_pyfunction!(
pyfunction_with_module_and_default_arg
))?;
m.add_function(pyo3::wrap_pyfunction!(
pyfunction_with_module_and_args_kwargs
))
}
#[test]
fn test_module_functions_with_module() {
let gil = Python::acquire_gil();
let py = gil.python();
let m = pyo3::wrap_pymodule!(module_with_functions_with_module)(py);
py_assert!(
py,
m,
"m.pyfunction_with_module() == 'module_with_functions_with_module'"
);
py_assert!(
py,
m,
"m.pyfunction_with_module_and_py() == 'module_with_functions_with_module'"
);
py_assert!(
py,
m,
"m.pyfunction_with_module_and_default_arg() \
== ('module_with_functions_with_module', 'foo')"
);
py_assert!(
py,
m,
"m.pyfunction_with_module_and_args_kwargs(1, x=1, y=2) \
== ('module_with_functions_with_module', 1, 2)"
);
}

View file

@ -14,7 +14,7 @@ fn test_optional_bool() {
// Regression test for issue #932
let gil = Python::acquire_gil();
let py = gil.python();
let f = wrap_pyfunction!(optional_bool)(py);
let f = wrap_pyfunction!(optional_bool)(py).unwrap();
py_assert!(py, f, "f() == 'Some(true)'");
py_assert!(py, f, "f(True) == 'Some(true)'");
@ -36,7 +36,7 @@ fn buffer_inplace_add(py: Python, x: PyBuffer<i32>, y: PyBuffer<i32>) {
fn test_buffer_add() {
let gil = Python::acquire_gil();
let py = gil.python();
let f = wrap_pyfunction!(buffer_inplace_add)(py);
let f = wrap_pyfunction!(buffer_inplace_add)(py).unwrap();
py_expect_exception!(
py,

View file

@ -14,7 +14,7 @@ fn test_unicode_encode_error() {
let gil = Python::acquire_gil();
let py = gil.python();
let take_str = wrap_pyfunction!(take_str)(py);
let take_str = wrap_pyfunction!(take_str)(py).unwrap();
py_run!(
py,
take_str,

View file

@ -110,7 +110,7 @@ fn test_function() {
let gil = Python::acquire_gil();
let py = gil.python();
let f = wrap_pyfunction!(my_function)(py);
let f = wrap_pyfunction!(my_function)(py).unwrap();
py_assert!(py, f, "f.__text_signature__ == '(a, b=None, *, c=42)'");
}

View file

@ -59,7 +59,7 @@ fn return_custom_class() {
assert_eq!(get_zero().unwrap().value, 0);
// Using from python
let get_zero = wrap_pyfunction!(get_zero)(py);
let get_zero = wrap_pyfunction!(get_zero)(py).unwrap();
py_assert!(py, get_zero, "get_zero().value == 0");
}
@ -206,5 +206,5 @@ fn result_conversion_function() -> Result<(), MyError> {
fn test_result_conversion() {
let gil = Python::acquire_gil();
let py = gil.python();
wrap_pyfunction!(result_conversion_function)(py);
wrap_pyfunction!(result_conversion_function)(py).unwrap();
}

View file

@ -0,0 +1,12 @@
use pyo3::prelude::*;
#[pymodule]
fn module(_py: Python, m: &PyModule) -> PyResult<()> {
#[pyfn(m, "with_module", pass_module)]
fn fail(string: &str, module: &PyModule) -> PyResult<&str> {
module.name()
}
Ok(())
}
fn main(){}

View file

@ -0,0 +1,5 @@
error: Expected &PyModule as first argument with `pass_module`.
--> $DIR/invalid_need_module_arg_position.rs:6:13
|
6 | fn fail(string: &str, module: &PyModule) -> PyResult<&str> {
| ^^^^^^^^^^^^