Merge branch 'master' into abi3
This commit is contained in:
commit
7a4c5e2960
11
.github/dependabot.yml
vendored
Normal file
11
.github/dependabot.yml
vendored
Normal 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"
|
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
|
@ -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]
|
||||
|
|
3
.github/workflows/guide.yml
vendored
3
.github/workflows/guide.yml
vendored
|
@ -6,6 +6,9 @@ on:
|
|||
- master
|
||||
release:
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
21
benches/bench_set.rs
Normal 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;
|
||||
}
|
||||
});
|
||||
}
|
|
@ -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%
|
||||
|
|
|
@ -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(())
|
||||
|
|
|
@ -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>()?;
|
||||
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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
1
guide/src/changelog.md
Normal file
|
@ -0,0 +1 @@
|
|||
{{#include ../../CHANGELOG.md}}
|
|
@ -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
|
||||
|
||||
|
|
303
guide/src/class/protocols.md
Normal file
303
guide/src/class/protocols.md
Normal 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
|
|
@ -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.
|
||||
|
|
96
guide/src/conversions/tables.md
Normal file
96
guide/src/conversions/tables.md
Normal 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.
|
324
guide/src/conversions/traits.md
Normal file
324
guide/src/conversions/traits.md
Normal 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
|
|
@ -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() {}
|
||||
```
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
```
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
||||
|
|
|
@ -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>`
|
||||
|
|
|
@ -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 = ¶ms.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>,
|
||||
|
|
|
@ -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! {
|
||||
#name(#(#names),*)
|
||||
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);
|
||||
|
||||
|
|
|
@ -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"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -488,58 +488,48 @@ 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 }
|
||||
};
|
||||
if let syn::Type::Reference(tref) = ty {
|
||||
let (tref, mut_) = preprocess_tref(tref, self_);
|
||||
// To support Rustc 1.39.0, we don't use as_deref here...
|
||||
let tmp_as_deref = if mut_.is_some() {
|
||||
quote! { _tmp.as_mut().map(std::ops::DerefMut::deref_mut) }
|
||||
} else {
|
||||
quote! { _tmp.as_ref().map(std::ops::Deref::deref) }
|
||||
};
|
||||
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") },
|
||||
};
|
||||
|
||||
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! {
|
||||
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;
|
||||
}
|
||||
(
|
||||
quote! { Option<<#tref as pyo3::derive_utils::ExtractExt>::Target> },
|
||||
// To support Rustc 1.39.0, we don't use as_deref here...
|
||||
if mut_.is_some() {
|
||||
quote! { _tmp.as_mut().map(std::ops::DerefMut::deref_mut) }
|
||||
} else {
|
||||
quote! { _tmp.as_ref().map(std::ops::Deref::deref) }
|
||||
},
|
||||
)
|
||||
} else {
|
||||
quote! {
|
||||
let #arg_name = match #arg_value {
|
||||
Some(_obj) => _obj.extract()?,
|
||||
None => #default,
|
||||
};
|
||||
}
|
||||
// Get &T from PyRef<T>
|
||||
(
|
||||
quote! { <#tref as pyo3::derive_utils::ExtractExt>::Target },
|
||||
quote! { &#mut_ *_tmp },
|
||||
)
|
||||
};
|
||||
|
||||
quote! {
|
||||
let #mut_ _tmp: #target_ty = match #arg_value {
|
||||
Some(_obj) => _obj.extract()?,
|
||||
None => #default,
|
||||
};
|
||||
let #arg_name = #borrow_tmp;
|
||||
}
|
||||
} else if let Some(default) = spec.default_value(name) {
|
||||
} else {
|
||||
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! {
|
||||
let #mut_ _tmp: <#tref as pyo3::derive_utils::ExtractExt>::Target
|
||||
= #arg_value.unwrap().extract()?;
|
||||
let #arg_name = &#mut_ *_tmp;
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
let #arg_name = #arg_value.unwrap().extract()?;
|
||||
}
|
||||
};
|
||||
|
||||
/// Replace `Self`, remove lifetime and get mutability from the type
|
||||
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(())
|
||||
//! }
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
};
|
||||
if ret < 0 {
|
||||
return Err(PyErr::fetch(py));
|
||||
}
|
||||
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(())
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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'");
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)"
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)'");
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
12
tests/ui/invalid_need_module_arg_position.rs
Normal file
12
tests/ui/invalid_need_module_arg_position.rs
Normal 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(){}
|
5
tests/ui/invalid_need_module_arg_position.stderr
Normal file
5
tests/ui/invalid_need_module_arg_position.stderr
Normal 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> {
|
||||
| ^^^^^^^^^^^^
|
Loading…
Reference in a new issue