" > target/banner.html
- cargo +nightly pyo3_doc_internal
+ cargo xtask doc --internal
cp -r target/doc gh-pages-build/internal
env:
RUSTDOCFLAGS: "--cfg docsrs --Z unstable-options --document-hidden-items --html-before-content target/banner.html"
@@ -55,7 +59,7 @@ jobs:
destination_dir: internal
full_commit_message: "Upload internal documentation"
- - name: Clear the extra artefacts created earlier
+ - name: Clear the extra artefacts created earlier
run: rm -rf target
# This builds the book in gh-pages-build. See https://github.com/rust-lang-nursery/mdBook/issues/698
@@ -67,7 +71,7 @@ jobs:
# This adds the docs to gh-pages-build/doc
- name: Build the doc
run: |
- cargo +nightly pyo3_doc_scrape
+ cargo xtask doc
cp -r target/doc gh-pages-build/doc
echo "" > gh-pages-build/doc/index.html
@@ -79,8 +83,9 @@ jobs:
publish_dir: ./gh-pages-build/
destination_dir: ${{ steps.prepare_tag.outputs.tag_name }}
full_commit_message: "Upload documentation for ${{ steps.prepare_tag.outputs.tag_name }}"
- release:
- needs: build
+
+ guide-release:
+ needs: guide-build
runs-on: ubuntu-latest
if: ${{ github.event_name == 'release' }}
steps:
@@ -99,3 +104,94 @@ jobs:
publish_dir: ./public/
full_commit_message: "Release ${{ needs.build.outputs.tag_name }}"
keep_files: true
+
+ cargo-benchmark:
+ if: ${{ github.ref_name == 'main' }}
+ name: Cargo benchmark
+ needs: guide-build
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+
+ - uses: actions-rs/toolchain@v1
+ with:
+ profile: minimal
+ toolchain: stable
+ override: true
+
+ - uses: actions/cache@v2
+ with:
+ path: |
+ ~/.cargo/registry
+ ~/.cargo/git
+ target
+ key: cargo-${{ runner.os }}-bench-${{ hashFiles('**/Cargo.toml') }}
+ continue-on-error: true
+
+ - name: Run benchmarks
+ run: |
+ for bench in call dict gil list pyclass pyobject set tuple; do
+ cargo bench --features hashbrown --bench "bench_$bench" -- --output-format bencher | tee -a output.txt
+ done
+
+ # Download previous benchmark result from cache (if exists)
+ - name: Download previous benchmark data
+ uses: actions/cache@v1
+ with:
+ path: ./cache
+ key: ${{ runner.os }}-benchmark
+
+ # Run `github-action-benchmark` action
+ - name: Store benchmark result
+ uses: benchmark-action/github-action-benchmark@v1
+ with:
+ name: pyo3-bench
+ # What benchmark tool the output.txt came from
+ tool: "cargo"
+ # Where the output from the benchmark tool is stored
+ output-file-path: output.txt
+ # GitHub API token to make a commit comment
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ auto-push: ${{ github.event_name != 'pull_request' }}
+
+ pytest-benchmark:
+ if: ${{ github.ref_name == 'main' }}
+ name: Pytest benchmark
+ needs: cargo-benchmark
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions/setup-python@v2
+ - uses: actions-rs/toolchain@v1
+ with:
+ profile: minimal
+ toolchain: stable
+ override: true
+
+ - uses: actions/cache@v2
+ with:
+ path: |
+ ~/.cargo/registry
+ ~/.cargo/git
+ target
+ key: cargo-${{ runner.os }}-pytest-bench-${{ hashFiles('**/Cargo.toml') }}
+ continue-on-error: true
+
+ - name: Download previous benchmark data
+ uses: actions/cache@v1
+ with:
+ path: ./cache
+ key: ${{ runner.os }}-pytest-benchmark
+
+ - name: Run benchmarks
+ run: |
+ pip install nox
+ nox -f pytests/noxfile.py -s bench -- --benchmark-json $(pwd)/output.json
+ - name: Store benchmark result
+ uses: benchmark-action/github-action-benchmark@v1
+ with:
+ name: pytest-bench
+ tool: "pytest"
+ output-file-path: output.json
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ auto-push: ${{ github.event_name != 'pull_request' }}
diff --git a/.gitignore b/.gitignore
index 7098c348..9e87d2a7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,3 +20,4 @@ guide/book/
extensions/stamps/
pip-wheel-metadata
valgrind-python.supp
+*.pyd
diff --git a/Architecture.md b/Architecture.md
index f46f6db5..8f26db4b 100644
--- a/Architecture.md
+++ b/Architecture.md
@@ -144,20 +144,12 @@ For example, you can see `type({})` shows `dict` and `type(type({}))` shows `typ
## 4. Protocol methods
-Python has some built-in special methods called dunder, such as `__iter__`.
-They are called [abstract objects layer](https://docs.python.org/3/c-api/abstract.html) in
+Python has some built-in special methods called dunder methods, such as `__iter__`.
+They are called "slots" in the [abstract objects layer](https://docs.python.org/3/c-api/abstract.html) in
Python/C API.
-We provide a way to implement those protocols by using `#[pyproto]` and specific traits, such
-as `PyIterProtocol`.
-[`src/class`] defines these traits.
-Each protocol method has a corresponding FFI function.
-For example, `PyIterProtocol::__iter__` has
-`pub unsafe extern "C" fn iter(slf: *mut PyObject) -> *mut PyObject`.
-When `#[pyproto]` finds that `T` implements `PyIterProtocol::__iter__`, it automatically
-sets `iter` on the type object of `T`.
-
-Also, [`src/class/methods.rs`] has utilities for `#[pyfunction]` and [`src/class/impl_.rs`] has
-some internal tricks for making `#[pyproto]` flexible.
+We provide a way to implement those protocols similarly, by recognizing special
+names in `#[pymethods]`, with a few new ones for slots that can not be
+implemented in Python, such as GC support.
## 5. Procedural macros to simplify usage for users.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d4ee9057..60bba62f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,7 +6,53 @@ PyO3 versions, please see the [migration guide](https://pyo3.rs/latest/migration
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
-## Unreleased
+## [Unreleased]
+
+### Added
+
+- Allow dependent crates to access config values from `pyo3-build-config` via cargo link dep env vars. [#2092](https://github.com/PyO3/pyo3/pull/2092)
+- Added methods on `InterpreterConfig` to run Python scripts using the configured executable. [#2092](https://github.com/PyO3/pyo3/pull/2092)
+- Added FFI definitions for `PyType_FromModuleAndSpec`, `PyType_GetModule`, `PyType_GetModuleState` and `PyModule_AddType`. [#2250](https://github.com/PyO3/pyo3/pull/2250)
+
+### Changed
+
+- Allow `#[pyo3(crate = "...", text_signature = "...")]` options to be used directly in `#[pyclass(crate = "...", text_signature = "...")]`. [#2234](https://github.com/PyO3/pyo3/pull/2234)
+- Mark `METH_FASTCALL` calling convention as limited API on Python 3.10. [#2250](https://github.com/PyO3/pyo3/pull/2250)
+
+### Fixed
+
+- Fix `abi3-py310` feature: use Python 3.10 ABI when available instead of silently falling back to the 3.9 ABI. [#2242](https://github.com/PyO3/pyo3/pull/2242)
+- Considered `PYTHONFRAMEWORK` when cross compiling in order that on macos cross compiling against a [Framework bundle](https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPFrameworks/Concepts/FrameworkAnatomy.html) is considered shared. [#2233](https://github.com/PyO3/pyo3/pull/2233)
+- Panic during compilation when `PYO3_CROSS_LIB_DIR` is set for some host/target combinations. [#2232](https://github.com/PyO3/pyo3/pull/2232)
+- Correct dependency version for `syn` to require correct minimal patch version 1.0.56. [#2240](https://github.com/PyO3/pyo3/pull/2240)
+
+### Added
+
+- Added `as_bytes` on `Py`. [#2235](https://github.com/PyO3/pyo3/pull/2235)
+
+### Packaging
+
+- Extend `parking_lot` dependency supported versions to include 0.12. [#2239](https://github.com/PyO3/pyo3/pull/2239)
+
+## [0.16.2] - 2022-03-15
+
+### Packaging
+
+- Warn when modules are imported on PyPy 3.7 versions older than PyPy 7.3.8, as they are known to have binary compatibility issues. [#2217](https://github.com/PyO3/pyo3/pull/2217)
+- Ensure build script of `pyo3-ffi` runs before that of `pyo3` to fix cross compilation. [#2224](https://github.com/PyO3/pyo3/pull/2224)
+
+## [0.16.1] - 2022-03-05
+
+### Packaging
+
+- Extend `hashbrown` optional dependency supported versions to include 0.12. [#2197](https://github.com/PyO3/pyo3/pull/2197)
+
+### Fixed
+
+- Fix incorrect platform detection for Windows in `pyo3-build-config`. [#2198](https://github.com/PyO3/pyo3/pull/2198)
+- Fix regression from 0.16 preventing cross compiling to aarch64 macOS. [#2201](https://github.com/PyO3/pyo3/pull/2201)
+
+## [0.16.0] - 2022-02-27
### Packaging
@@ -16,21 +62,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `pyo3-build-config` no longer enables the `resolve-config` feature by default. [#2008](https://github.com/PyO3/pyo3/pull/2008)
- Update `inventory` optional dependency to 0.2. [#2019](https://github.com/PyO3/pyo3/pull/2019)
- Drop `paste` dependency. [#2081](https://github.com/PyO3/pyo3/pull/2081)
-- The bindings found `pyo3::ffi` are now a re-export of the new `pyo3-ffi` crate. [#2126](https://github.com/PyO3/pyo3/pull/2126)
+- The bindings found in `pyo3::ffi` are now a re-export of a separate `pyo3-ffi` crate. [#2126](https://github.com/PyO3/pyo3/pull/2126)
+- Support PyPy 3.9. [#2143](https://github.com/PyO3/pyo3/pull/2143)
### Added
+- Add `PyCapsule` type exposing the [Capsule API](https://docs.python.org/3/c-api/capsule.html#capsules). [#1980](https://github.com/PyO3/pyo3/pull/1980)
+- Add `pyo3_build_config::Sysconfigdata` and supporting APIs. [#1996](https://github.com/PyO3/pyo3/pull/1996)
- Add `Py::setattr` method. [#2009](https://github.com/PyO3/pyo3/pull/2009)
-- Add `PyCapsule`, exposing the [Capsule API](https://docs.python.org/3/c-api/capsule.html#capsules). [#1980](https://github.com/PyO3/pyo3/pull/1980)
-- Expose `pyo3-build-config` APIs for cross-compiling and Python configuration discovery for use in other projects. [#1996](https://github.com/PyO3/pyo3/pull/1996)
-- All PyO3 proc-macros except the deprecated `#[pyproto]` now accept a supplemental attribute `#[pyo3(crate = "some::path")]` that specifies where to find the `pyo3` crate, in case it has been renamed or is re-exported and not found at the crate root. [#2022](https://github.com/PyO3/pyo3/pull/2022)
+- Add `#[pyo3(crate = "some::path")]` option to all attribute macros (except the deprecated `#[pyproto]`). [#2022](https://github.com/PyO3/pyo3/pull/2022)
+- Enable `create_exception!` macro to take an optional docstring. [#2027](https://github.com/PyO3/pyo3/pull/2027)
- Enable `#[pyclass]` for fieldless (aka C-like) enums. [#2034](https://github.com/PyO3/pyo3/pull/2034)
- Add buffer magic methods `__getbuffer__` and `__releasebuffer__` to `#[pymethods]`. [#2067](https://github.com/PyO3/pyo3/pull/2067)
-- Accept paths in `wrap_pyfunction` and `wrap_pymodule`. [#2081](https://github.com/PyO3/pyo3/pull/2081)
-- Add check for correct number of arguments on magic methods. [#2083](https://github.com/PyO3/pyo3/pull/2083)
-- `wrap_pyfunction!` can now wrap a `#[pyfunction]` which is implemented in a different Rust module or crate. [#2091](https://github.com/PyO3/pyo3/pull/2091)
+- Add support for paths in `wrap_pyfunction` and `wrap_pymodule`. [#2081](https://github.com/PyO3/pyo3/pull/2081)
+- Enable `wrap_pyfunction!` to wrap a `#[pyfunction]` implemented in a different Rust module or crate. [#2091](https://github.com/PyO3/pyo3/pull/2091)
- Add `PyAny::contains` method (`in` operator for `PyAny`). [#2115](https://github.com/PyO3/pyo3/pull/2115)
- Add `PyMapping::contains` method (`in` operator for `PyMapping`). [#2133](https://github.com/PyO3/pyo3/pull/2133)
+- Add garbage collection magic magic methods `__traverse__` and `__clear__` to `#[pymethods]`. [#2159](https://github.com/PyO3/pyo3/pull/2159)
+- Add support for `from_py_with` on struct tuples and enums to override the default from-Python conversion. [#2181](https://github.com/PyO3/pyo3/pull/2181)
+- Add `eq`, `ne`, `lt`, `le`, `gt`, `ge` methods to `PyAny` that wrap `rich_compare`. [#2175](https://github.com/PyO3/pyo3/pull/2175)
+- Add `Py::is` and `PyAny::is` methods to check for object identity. [#2183](https://github.com/PyO3/pyo3/pull/2183)
+- Add support for the `__getattribute__` magic method. [#2187](https://github.com/PyO3/pyo3/pull/2187)
### Changed
@@ -41,19 +93,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `ptraceback` -> `traceback`
- `from_instance` -> `from_value`
- `into_instance` -> `into_value`
+- `PyErr::new_type` now takes an optional docstring and now returns `PyResult>` rather than a `ffi::PyTypeObject` pointer. [#2027](https://github.com/PyO3/pyo3/pull/2027)
- Deprecate `PyType::is_instance`; it is inconsistent with other `is_instance` methods in PyO3. Instead of `typ.is_instance(obj)`, use `obj.is_instance(typ)`. [#2031](https://github.com/PyO3/pyo3/pull/2031)
-- `PyErr::new_type` now takes an optional docstring and now returns `PyResult>` rather than a `ffi::PyTypeObject` pointer.
-- The `create_exception!` macro can now take an optional docstring. This docstring, if supplied, is visible to users (with `.__doc__` and `help()`) and
- accompanies your error type in your crate's documentation.
- `__getitem__`, `__setitem__` and `__delitem__` in `#[pymethods]` now implement both a Python mapping and sequence by default. [#2065](https://github.com/PyO3/pyo3/pull/2065)
- Improve performance and error messages for `#[derive(FromPyObject)]` for enums. [#2068](https://github.com/PyO3/pyo3/pull/2068)
- Reduce generated LLVM code size (to improve compile times) for:
- - internal `handle_panic` helper [#2074](https://github.com/PyO3/pyo3/pull/2074)
+ - internal `handle_panic` helper [#2074](https://github.com/PyO3/pyo3/pull/2074) [#2158](https://github.com/PyO3/pyo3/pull/2158)
- `#[pyfunction]` and `#[pymethods]` argument extraction [#2075](https://github.com/PyO3/pyo3/pull/2075) [#2085](https://github.com/PyO3/pyo3/pull/2085)
- - `#[pyclass]` type object creation [#2076](https://github.com/PyO3/pyo3/pull/2076) [#2081](https://github.com/PyO3/pyo3/pull/2081)
-- `__ipow__` now supports modulo argument on Python 3.8+. [#2083](https://github.com/PyO3/pyo3/pull/2083)
-- `pyo3-macros-backend` is now compiled with PyO3 cfgs to enable different magic method definitions based on version. [#2083](https://github.com/PyO3/pyo3/pull/2083)
-- `_PyCFunctionFast` now correctly reflects the signature defined in the [Python docs](https://docs.python.org/3/c-api/structures.html#c._PyCFunctionFast). [#2126](https://github.com/PyO3/pyo3/pull/2126)
+ - `#[pyclass]` type object creation [#2076](https://github.com/PyO3/pyo3/pull/2076) [#2081](https://github.com/PyO3/pyo3/pull/2081) [#2157](https://github.com/PyO3/pyo3/pull/2157)
+- Respect Rust privacy rules for items wrapped with `wrap_pyfunction` and `wrap_pymodule`. [#2081](https://github.com/PyO3/pyo3/pull/2081)
+- Add modulo argument to `__ipow__` magic method. [#2083](https://github.com/PyO3/pyo3/pull/2083)
+- Fix FFI definition for `_PyCFunctionFast`. [#2126](https://github.com/PyO3/pyo3/pull/2126)
- `PyDateTimeAPI` and `PyDateTime_TimeZone_UTC` are are now unsafe functions instead of statics. [#2126](https://github.com/PyO3/pyo3/pull/2126)
- `PyDateTimeAPI` does not implicitly call `PyDateTime_IMPORT` anymore to reflect the original Python API more closely. Before the first call to `PyDateTime_IMPORT` a null pointer is returned. Therefore before calling any of the following FFI functions `PyDateTime_IMPORT` must be called to avoid undefined behaviour: [#2126](https://github.com/PyO3/pyo3/pull/2126)
- `PyDateTime_TimeZone_UTC`
@@ -69,10 +119,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `PyTZInfo_CheckExact`
- `PyDateTime_FromTimestamp`
- `PyDate_FromTimestamp`
+- Deprecate the `gc` option for `pyclass` (e.g. `#[pyclass(gc)]`). Just implement a `__traverse__` `#[pymethod]`. [#2159](https://github.com/PyO3/pyo3/pull/2159)
+- The `ml_meth` field of `PyMethodDef` is now represented by the `PyMethodDefPointer` union. [2166](https://github.com/PyO3/pyo3/pull/2166)
+- Deprecate the `#[pyproto]` traits. [#2173](https://github.com/PyO3/pyo3/pull/2173)
### Removed
- Remove all functionality deprecated in PyO3 0.14. [#2007](https://github.com/PyO3/pyo3/pull/2007)
+- Remove `Default` impl for `PyMethodDef`. [#2166](https://github.com/PyO3/pyo3/pull/2166)
+- Remove `PartialEq` impl for `Py` and `PyAny` (use the new `is()` instead). [#2183](https://github.com/PyO3/pyo3/pull/2183)
### Fixed
@@ -80,10 +135,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fix memory leak in `PyErr::into_value`. [#2026](https://github.com/PyO3/pyo3/pull/2026)
- Fix clippy warning `needless-option-as-deref` in code generated by `#[pyfunction]` and `#[pymethods]`. [#2040](https://github.com/PyO3/pyo3/pull/2040)
- Fix undefined behavior in `PySlice::indices`. [#2061](https://github.com/PyO3/pyo3/pull/2061)
-- Use the Rust function path for `wrap_pymodule!` of a `#[pymodule]` with a `#[pyo3(name = "..")]` attribute, not the Python name. [#2081](https://github.com/PyO3/pyo3/pull/2081)
+- Fix the `wrap_pymodule!` macro using the wrong name for a `#[pymodule]` with a `#[pyo3(name = "..")]` attribute. [#2081](https://github.com/PyO3/pyo3/pull/2081)
+- Fix magic methods in `#[pymethods]` accepting implementations with the wrong number of arguments. [#2083](https://github.com/PyO3/pyo3/pull/2083)
- Fix panic in `#[pyfunction]` generated code when a required argument following an `Option` was not provided. [#2093](https://github.com/PyO3/pyo3/pull/2093)
- Fixed undefined behaviour caused by incorrect `ExactSizeIterator` implementations. [#2124](https://github.com/PyO3/pyo3/pull/2124)
+- Fix missing FFI definition `PyCMethod_New` on Python 3.9 and up. [#2143](https://github.com/PyO3/pyo3/pull/2143)
- Add missing FFI definitions `_PyLong_NumBits` and `_PyLong_AsByteArray` on PyPy. [#2146](https://github.com/PyO3/pyo3/pull/2146)
+- Fix memory leak in implementation of `AsPyPointer` for `Option`. [#2160](https://github.com/PyO3/pyo3/pull/2160)
+- Fix FFI definition of `_PyLong_NumBits` to return `size_t` instead of `c_int`. [#2161](https://github.com/PyO3/pyo3/pull/2161)
+- Fix `TypeError` thrown when argument parsing failed missing the originating causes. [2177](https://github.com/PyO3/pyo3/pull/2178)
## [0.15.1] - 2021-11-19
@@ -1066,7 +1126,10 @@ Yanked
- Initial release
-[unreleased]: https://github.com/pyo3/pyo3/compare/v0.15.1...HEAD
+[Unreleased]: https://github.com/pyo3/pyo3/compare/v0.16.2...HEAD
+[0.16.2]: https://github.com/pyo3/pyo3/compare/v0.16.1...v0.16.2
+[0.16.1]: https://github.com/pyo3/pyo3/compare/v0.16.0...v0.16.1
+[0.16.0]: https://github.com/pyo3/pyo3/compare/v0.15.1...v0.16.0
[0.15.1]: https://github.com/pyo3/pyo3/compare/v0.15.0...v0.15.1
[0.15.0]: https://github.com/pyo3/pyo3/compare/v0.14.5...v0.15.0
[0.14.5]: https://github.com/pyo3/pyo3/compare/v0.14.4...v0.14.5
diff --git a/Cargo.toml b/Cargo.toml
index fe18ed7f..bf66abf5 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "pyo3"
-version = "0.15.1"
+version = "0.16.2"
description = "Bindings to Python interpreter"
authors = ["PyO3 Project and Contributors "]
readme = "README.md"
@@ -12,18 +12,17 @@ categories = ["api-bindings", "development-tools::ffi"]
license = "Apache-2.0"
exclude = ["/.gitignore", ".cargo/config", "/codecov.yml", "/Makefile", "/pyproject.toml", "/noxfile.py"]
edition = "2018"
-links = "python"
[dependencies]
cfg-if = "1.0"
libc = "0.2.62"
-parking_lot = "0.11.0"
+parking_lot = ">= 0.11, < 0.13"
# ffi bindings to the python interpreter, split into a seperate crate so they can be used independently
-pyo3-ffi = { path = "pyo3-ffi", version = "=0.15.1" }
+pyo3-ffi = { path = "pyo3-ffi", version = "=0.16.2" }
# support crates for macros feature
-pyo3-macros = { path = "pyo3-macros", version = "=0.15.1", optional = true }
+pyo3-macros = { path = "pyo3-macros", version = "=0.16.2", optional = true }
indoc = { version = "1.0.3", optional = true }
unindent = { version = "0.1.4", optional = true }
@@ -33,7 +32,7 @@ inventory = { version = "0.2.0", optional = true }
# crate integrations that can be added using the eponymous features
anyhow = { version = "1.0", optional = true }
eyre = { version = ">= 0.4, < 0.7", optional = true }
-hashbrown = { version = ">= 0.9, < 0.12", optional = true }
+hashbrown = { version = ">= 0.9, < 0.13", optional = true }
indexmap = { version = ">= 1.6, < 1.8", optional = true }
num-bigint = { version = "0.4", optional = true }
num-complex = { version = ">= 0.2, < 0.5", optional = true }
@@ -46,10 +45,12 @@ trybuild = "1.0.49"
rustversion = "1.0"
# 1.0.0 requires Rust 1.50
proptest = { version = "0.10.1", default-features = false, features = ["std"] }
+send_wrapper = "0.5"
+serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.61"
[build-dependencies]
-pyo3-build-config = { path = "pyo3-build-config", version = "0.15.1", features = ["resolve-config"] }
+pyo3-build-config = { path = "pyo3-build-config", version = "0.16.2", features = ["resolve-config"] }
[features]
default = ["macros", "pyproto"]
@@ -69,7 +70,7 @@ pyproto = ["pyo3-macros/pyproto"]
extension-module = ["pyo3-ffi/extension-module"]
# Use the Python limited API. See https://www.python.org/dev/peps/pep-0384/ for more.
-abi3 = ["pyo3-build-config/abi3", "pyo3-ffi/abi3"]
+abi3 = ["pyo3-build-config/abi3", "pyo3-ffi/abi3", "pyo3-macros/abi3"]
# With abi3, we can manually set the minimum Python version.
abi3-py37 = ["abi3-py38", "pyo3-build-config/abi3-py37", "pyo3-ffi/abi3-py37"]
@@ -86,7 +87,18 @@ nightly = []
# Activates all additional features
# This is mostly intended for testing purposes - activating *all* of these isn't particularly useful.
-full = ["macros", "pyproto", "multiple-pymethods", "num-bigint", "num-complex", "hashbrown", "serde", "indexmap", "eyre", "anyhow"]
+full = [
+ "macros",
+ "pyproto",
+ "multiple-pymethods",
+ "num-bigint",
+ "num-complex",
+ "hashbrown",
+ "serde",
+ "indexmap",
+ "eyre",
+ "anyhow",
+]
[[bench]]
name = "bench_call"
diff --git a/Contributing.md b/Contributing.md
index 4fc2c246..a48be238 100644
--- a/Contributing.md
+++ b/Contributing.md
@@ -48,7 +48,7 @@ There are some specific areas of focus where help is currently needed for the do
- Not all APIs had docs or examples when they were made. The goal is to have documentation on all PyO3 APIs ([#306](https://github.com/PyO3/pyo3/issues/306)). If you see an API lacking a doc, please write one and open a PR!
You can build the docs (including all features) with
-```cargo +nightly pyo3_doc_scrape```
+```cargo xtask doc --open```
#### Doctests
@@ -87,6 +87,10 @@ Tests run with all supported Python versions with the latest stable Rust compile
If you are adding a new feature, you should add it to the `full` feature in our *Cargo.toml** so that it is tested in CI.
+You can run these tests yourself with
+```cargo xtask ci```
+See [it's documentation](https://github.com/PyO3/pyo3/tree/main/xtask#readme)for more commands you can run.
+
## Python and Rust version support policy
PyO3 aims to keep sufficient compatibility to make packaging Python extensions built with PyO3 feasible on most common package managers.
diff --git a/Makefile b/Makefile
deleted file mode 100644
index 668807e3..00000000
--- a/Makefile
+++ /dev/null
@@ -1,41 +0,0 @@
-.PHONY: test test_py publish clippy lint fmt fmt_py fmt_rust
-
-ALL_ADDITIVE_FEATURES = macros multiple-pymethods num-bigint num-complex hashbrown serde indexmap eyre anyhow
-COVERAGE_PACKAGES = --package pyo3 --package pyo3-build-config --package pyo3-macros-backend --package pyo3-macros
-
-test: lint test_py
- cargo test
- cargo test --features="abi3"
- cargo test --features="$(ALL_ADDITIVE_FEATURES)"
- cargo test --features="abi3 $(ALL_ADDITIVE_FEATURES)"
-
-test_py:
- @for example in examples/*/noxfile.py; do echo "-- Running nox for $$example --"; nox -f $$example/noxfile.py || exit 1; echo ""; done
- echo "-- Running nox for pytests/noxfile.py --";
- nox -f pytests/noxfile.py || exit 1;
-
-fmt_py:
- black . --check
-
-fmt_rust:
- cargo fmt --all -- --check
-
-fmt: fmt_rust fmt_py
- @true
-
-clippy:
- cargo clippy --features="$(ALL_ADDITIVE_FEATURES)" --all-targets --workspace -- -Dwarnings
- cargo clippy --features="abi3 $(ALL_ADDITIVE_FEATURES)" --all-targets --workspace -- -Dwarnings
- for example in examples/*/; do cargo clippy --manifest-path $$example/Cargo.toml -- -Dwarnings || exit 1; done
-
-lint: fmt clippy
- @true
-
-publish: test
- cargo publish --manifest-path pyo3-build-config/Cargo.toml
- sleep 10
- cargo publish --manifest-path pyo3-macros-backend/Cargo.toml
- sleep 10 # wait for crates.io to update
- cargo publish --manifest-path pyo3-macros/Cargo.toml
- sleep 10 # wait for crates.io to update
- cargo publish
diff --git a/README.md b/README.md
index 2e1b8b3c..52e49cea 100644
--- a/README.md
+++ b/README.md
@@ -56,6 +56,9 @@ version = "0.1.0"
edition = "2018"
[lib]
+# The name of the native library. This is the name which will be used in Python to import the
+# library (i.e. `import string_sum`). If you change this, you must also change the name of the
+# `#[pymodule]` in `src/lib.rs`.
name = "string_sum"
# "cdylib" is necessary to produce a shared library for Python to import from.
#
@@ -65,7 +68,7 @@ name = "string_sum"
crate-type = ["cdylib"]
[dependencies]
-pyo3 = { version = "0.15.1", features = ["extension-module"] }
+pyo3 = { version = "0.16.2", features = ["extension-module"] }
```
**`src/lib.rs`**
@@ -83,7 +86,7 @@ fn sum_as_string(a: usize, b: usize) -> PyResult {
/// the `lib.name` setting in the `Cargo.toml`, else Python will not be able to
/// import the module.
#[pymodule]
-fn string_sum(_py: Python, m: &PyModule) -> PyResult<()> {
+fn string_sum(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
Ok(())
}
@@ -129,7 +132,7 @@ Start a new project with `cargo new` and add `pyo3` to the `Cargo.toml` like th
```toml
[dependencies.pyo3]
-version = "0.15.1"
+version = "0.16.2"
features = ["auto-initialize"]
```
@@ -166,7 +169,8 @@ about this topic.
- [dict-derive](https://github.com/gperinazzo/dict-derive) _Derive FromPyObject to automatically transform Python dicts into Rust structs_
- [pyo3-log](https://github.com/vorner/pyo3-log) _Bridge from Rust to Python logging_
- [pythonize](https://github.com/davidhewitt/pythonize) _Serde serializer for converting Rust objects to JSON-compatible Python objects_
-- [pyo3-asyncio](https://github.com/awestlake87/pyo3-asyncio) Utilities for working with Python's Asyncio library and async functions
+- [pyo3-asyncio](https://github.com/awestlake87/pyo3-asyncio) _Utilities for working with Python's Asyncio library and async functions_
+- [rustimport](https://github.com/mityax/rustimport) _Directly import Rust files or crates from Python, without manual compilation step. Provides pyo3 integration by default and generates pyo3 binding code automatically._
## Examples
diff --git a/benches/bench_call.rs b/benches/bench_call.rs
index 90c1a7ff..dd98c159 100644
--- a/benches/bench_call.rs
+++ b/benches/bench_call.rs
@@ -8,7 +8,7 @@ macro_rules! test_module {
};
}
-fn bench_call_0(b: &mut Bencher) {
+fn bench_call_0(b: &mut Bencher<'_>) {
Python::with_gil(|py| {
let module = test_module!(py, "def foo(): pass");
@@ -22,7 +22,7 @@ fn bench_call_0(b: &mut Bencher) {
})
}
-fn bench_call_method_0(b: &mut Bencher) {
+fn bench_call_method_0(b: &mut Bencher<'_>) {
Python::with_gil(|py| {
let module = test_module!(
py,
diff --git a/benches/bench_dict.rs b/benches/bench_dict.rs
index e8d98939..7fea4272 100644
--- a/benches/bench_dict.rs
+++ b/benches/bench_dict.rs
@@ -4,7 +4,7 @@ use pyo3::prelude::*;
use pyo3::types::IntoPyDict;
use std::collections::{BTreeMap, HashMap};
-fn iter_dict(b: &mut Bencher) {
+fn iter_dict(b: &mut Bencher<'_>) {
let gil = Python::acquire_gil();
let py = gil.python();
const LEN: usize = 100_000;
@@ -18,14 +18,14 @@ fn iter_dict(b: &mut Bencher) {
});
}
-fn dict_new(b: &mut Bencher) {
+fn dict_new(b: &mut Bencher<'_>) {
let gil = Python::acquire_gil();
let py = gil.python();
const LEN: usize = 50_000;
b.iter(|| (0..LEN as u64).map(|i| (i, i * 2)).into_py_dict(py));
}
-fn dict_get_item(b: &mut Bencher) {
+fn dict_get_item(b: &mut Bencher<'_>) {
let gil = Python::acquire_gil();
let py = gil.python();
const LEN: usize = 50_000;
@@ -38,7 +38,7 @@ fn dict_get_item(b: &mut Bencher) {
});
}
-fn extract_hashmap(b: &mut Bencher) {
+fn extract_hashmap(b: &mut Bencher<'_>) {
let gil = Python::acquire_gil();
let py = gil.python();
const LEN: usize = 100_000;
@@ -46,7 +46,7 @@ fn extract_hashmap(b: &mut Bencher) {
b.iter(|| HashMap::::extract(dict));
}
-fn extract_btreemap(b: &mut Bencher) {
+fn extract_btreemap(b: &mut Bencher<'_>) {
let gil = Python::acquire_gil();
let py = gil.python();
const LEN: usize = 100_000;
@@ -55,7 +55,7 @@ fn extract_btreemap(b: &mut Bencher) {
}
#[cfg(feature = "hashbrown")]
-fn extract_hashbrown_map(b: &mut Bencher) {
+fn extract_hashbrown_map(b: &mut Bencher<'_>) {
let gil = Python::acquire_gil();
let py = gil.python();
const LEN: usize = 100_000;
diff --git a/benches/bench_err.rs b/benches/bench_err.rs
index a0318d14..e3194512 100644
--- a/benches/bench_err.rs
+++ b/benches/bench_err.rs
@@ -2,7 +2,7 @@ use criterion::{criterion_group, criterion_main, Bencher, Criterion};
use pyo3::{exceptions::PyValueError, prelude::*};
-fn err_new_restore_and_fetch(b: &mut Bencher) {
+fn err_new_restore_and_fetch(b: &mut Bencher<'_>) {
Python::with_gil(|py| {
b.iter(|| {
PyValueError::new_err("some exception message").restore(py);
@@ -11,7 +11,7 @@ fn err_new_restore_and_fetch(b: &mut Bencher) {
})
}
-fn err_new_without_gil(b: &mut Bencher) {
+fn err_new_without_gil(b: &mut Bencher<'_>) {
b.iter(|| PyValueError::new_err("some exception message"))
}
diff --git a/benches/bench_frompyobject.rs b/benches/bench_frompyobject.rs
index 7f1274a8..8cf8a4da 100644
--- a/benches/bench_frompyobject.rs
+++ b/benches/bench_frompyobject.rs
@@ -9,7 +9,7 @@ enum ManyTypes {
String(String),
}
-fn enum_from_pyobject(b: &mut Bencher) {
+fn enum_from_pyobject(b: &mut Bencher<'_>) {
Python::with_gil(|py| {
let obj = PyString::new(py, "hello world");
b.iter(|| {
diff --git a/benches/bench_gil.rs b/benches/bench_gil.rs
index 8dfed873..1dc1e625 100644
--- a/benches/bench_gil.rs
+++ b/benches/bench_gil.rs
@@ -2,7 +2,7 @@ use criterion::{criterion_group, criterion_main, BatchSize, Bencher, Criterion};
use pyo3::{prelude::*, GILPool};
-fn bench_clean_gilpool_new(b: &mut Bencher) {
+fn bench_clean_gilpool_new(b: &mut Bencher<'_>) {
Python::with_gil(|_py| {
b.iter(|| {
let _ = unsafe { GILPool::new() };
@@ -10,14 +10,14 @@ fn bench_clean_gilpool_new(b: &mut Bencher) {
});
}
-fn bench_clean_acquire_gil(b: &mut Bencher) {
+fn bench_clean_acquire_gil(b: &mut Bencher<'_>) {
// Acquiring first GIL will also create a "clean" GILPool, so this measures the Python overhead.
b.iter(|| {
let _ = Python::acquire_gil();
});
}
-fn bench_dirty_acquire_gil(b: &mut Bencher) {
+fn bench_dirty_acquire_gil(b: &mut Bencher<'_>) {
let obj = Python::with_gil(|py| py.None());
b.iter_batched(
|| {
diff --git a/benches/bench_list.rs b/benches/bench_list.rs
index 12fd4510..b52f9890 100644
--- a/benches/bench_list.rs
+++ b/benches/bench_list.rs
@@ -3,7 +3,7 @@ use criterion::{criterion_group, criterion_main, Bencher, Criterion};
use pyo3::prelude::*;
use pyo3::types::PyList;
-fn iter_list(b: &mut Bencher) {
+fn iter_list(b: &mut Bencher<'_>) {
let gil = Python::acquire_gil();
let py = gil.python();
const LEN: usize = 100_000;
@@ -17,14 +17,14 @@ fn iter_list(b: &mut Bencher) {
});
}
-fn list_new(b: &mut Bencher) {
+fn list_new(b: &mut Bencher<'_>) {
let gil = Python::acquire_gil();
let py = gil.python();
const LEN: usize = 50_000;
b.iter(|| PyList::new(py, 0..LEN));
}
-fn list_get_item(b: &mut Bencher) {
+fn list_get_item(b: &mut Bencher<'_>) {
let gil = Python::acquire_gil();
let py = gil.python();
const LEN: usize = 50_000;
@@ -38,7 +38,7 @@ fn list_get_item(b: &mut Bencher) {
}
#[cfg(not(Py_LIMITED_API))]
-fn list_get_item_unchecked(b: &mut Bencher) {
+fn list_get_item_unchecked(b: &mut Bencher<'_>) {
let gil = Python::acquire_gil();
let py = gil.python();
const LEN: usize = 50_000;
diff --git a/benches/bench_pyclass.rs b/benches/bench_pyclass.rs
index 04676b1a..2d6868d4 100644
--- a/benches/bench_pyclass.rs
+++ b/benches/bench_pyclass.rs
@@ -1,5 +1,5 @@
use criterion::{criterion_group, criterion_main, Criterion};
-use pyo3::{class::PyObjectProtocol, prelude::*, type_object::LazyStaticType};
+use pyo3::{prelude::*, type_object::LazyStaticType};
/// This is a feature-rich class instance used to benchmark various parts of the pyclass lifecycle.
#[pyclass]
@@ -19,17 +19,14 @@ impl MyClass {
self.elements.push(new_element);
self.elements.len()
}
-}
-#[pyproto]
-impl PyObjectProtocol for MyClass {
/// A basic __str__ implementation.
fn __str__(&self) -> &'static str {
"MyClass"
}
}
-pub fn first_time_init(b: &mut criterion::Bencher) {
+pub fn first_time_init(b: &mut criterion::Bencher<'_>) {
let gil = Python::acquire_gil();
let py = gil.python();
b.iter(|| {
diff --git a/benches/bench_pyobject.rs b/benches/bench_pyobject.rs
index bc507f90..fb17c050 100644
--- a/benches/bench_pyobject.rs
+++ b/benches/bench_pyobject.rs
@@ -2,7 +2,7 @@ use criterion::{criterion_group, criterion_main, Bencher, Criterion};
use pyo3::prelude::*;
-fn drop_many_objects(b: &mut Bencher) {
+fn drop_many_objects(b: &mut Bencher<'_>) {
let gil = Python::acquire_gil();
let py = gil.python();
b.iter(|| {
diff --git a/benches/bench_set.rs b/benches/bench_set.rs
index 7615bb84..698f6eb3 100644
--- a/benches/bench_set.rs
+++ b/benches/bench_set.rs
@@ -4,7 +4,7 @@ use pyo3::prelude::*;
use pyo3::types::PySet;
use std::collections::{BTreeSet, HashSet};
-fn iter_set(b: &mut Bencher) {
+fn iter_set(b: &mut Bencher<'_>) {
let gil = Python::acquire_gil();
let py = gil.python();
const LEN: usize = 100_000;
@@ -18,7 +18,7 @@ fn iter_set(b: &mut Bencher) {
});
}
-fn extract_hashset(b: &mut Bencher) {
+fn extract_hashset(b: &mut Bencher<'_>) {
let gil = Python::acquire_gil();
let py = gil.python();
const LEN: usize = 100_000;
@@ -26,7 +26,7 @@ fn extract_hashset(b: &mut Bencher) {
b.iter(|| HashSet::::extract(set));
}
-fn extract_btreeset(b: &mut Bencher) {
+fn extract_btreeset(b: &mut Bencher<'_>) {
let gil = Python::acquire_gil();
let py = gil.python();
const LEN: usize = 100_000;
@@ -35,7 +35,7 @@ fn extract_btreeset(b: &mut Bencher) {
}
#[cfg(feature = "hashbrown")]
-fn extract_hashbrown_set(b: &mut Bencher) {
+fn extract_hashbrown_set(b: &mut Bencher<'_>) {
let gil = Python::acquire_gil();
let py = gil.python();
const LEN: usize = 100_000;
diff --git a/benches/bench_tuple.rs b/benches/bench_tuple.rs
index 29724318..4a5eb8b5 100644
--- a/benches/bench_tuple.rs
+++ b/benches/bench_tuple.rs
@@ -3,7 +3,7 @@ use criterion::{criterion_group, criterion_main, Bencher, Criterion};
use pyo3::prelude::*;
use pyo3::types::PyTuple;
-fn iter_tuple(b: &mut Bencher) {
+fn iter_tuple(b: &mut Bencher<'_>) {
let gil = Python::acquire_gil();
let py = gil.python();
const LEN: usize = 100_000;
@@ -17,14 +17,14 @@ fn iter_tuple(b: &mut Bencher) {
});
}
-fn tuple_new(b: &mut Bencher) {
+fn tuple_new(b: &mut Bencher<'_>) {
let gil = Python::acquire_gil();
let py = gil.python();
const LEN: usize = 50_000;
b.iter(|| PyTuple::new(py, 0..LEN));
}
-fn tuple_get_item(b: &mut Bencher) {
+fn tuple_get_item(b: &mut Bencher<'_>) {
let gil = Python::acquire_gil();
let py = gil.python();
const LEN: usize = 50_000;
@@ -38,7 +38,7 @@ fn tuple_get_item(b: &mut Bencher) {
}
#[cfg(not(Py_LIMITED_API))]
-fn tuple_get_item_unchecked(b: &mut Bencher) {
+fn tuple_get_item_unchecked(b: &mut Bencher<'_>) {
let gil = Python::acquire_gil();
let py = gil.python();
const LEN: usize = 50_000;
diff --git a/build.rs b/build.rs
index cfaf06f6..78cebf9a 100644
--- a/build.rs
+++ b/build.rs
@@ -1,7 +1,7 @@
-use std::{env, process::Command};
+use std::env;
use pyo3_build_config::pyo3_build_script_impl::{cargo_env_var, errors::Result};
-use pyo3_build_config::{bail, InterpreterConfig};
+use pyo3_build_config::{bail, print_feature_cfgs, InterpreterConfig};
fn ensure_auto_initialize_ok(interpreter_config: &InterpreterConfig) -> Result<()> {
if cargo_env_var("CARGO_FEATURE_AUTO_INITIALIZE").is_some() {
@@ -32,17 +32,6 @@ fn ensure_auto_initialize_ok(interpreter_config: &InterpreterConfig) -> Result<(
Ok(())
}
-fn rustc_minor_version() -> Option {
- let rustc = env::var_os("RUSTC")?;
- let output = Command::new(rustc).arg("--version").output().ok()?;
- let version = core::str::from_utf8(&output.stdout).ok()?;
- let mut pieces = version.split('.');
- if pieces.next() != Some("rustc 1") {
- return None;
- }
- pieces.next()?.parse().ok()
-}
-
/// Prepares the PyO3 crate for compilation.
///
/// This loads the config from pyo3-build-config and then makes some additional checks to improve UX
@@ -55,18 +44,10 @@ fn configure_pyo3() -> Result<()> {
interpreter_config.emit_pyo3_cfgs();
- let rustc_minor_version = rustc_minor_version().unwrap_or(0);
ensure_auto_initialize_ok(interpreter_config)?;
- // Enable use of const generics on Rust 1.51 and greater
- if rustc_minor_version >= 51 {
- println!("cargo:rustc-cfg=min_const_generics");
- }
-
- // Enable use of std::ptr::addr_of! on Rust 1.51 and greater
- if rustc_minor_version >= 51 {
- println!("cargo:rustc-cfg=addr_of");
- }
+ // Emit cfgs like `addr_of` and `min_const_generics`
+ print_feature_cfgs();
Ok(())
}
diff --git a/examples/Cargo.toml b/examples/Cargo.toml
index 6ad255f6..93c6a6f5 100644
--- a/examples/Cargo.toml
+++ b/examples/Cargo.toml
@@ -5,7 +5,7 @@ publish = false
edition = "2018"
[dev-dependencies]
-pyo3 = { version = "0.15.1", path = "..", features = ["auto-initialize", "extension-module"] }
+pyo3 = { version = "0.16.2", path = "..", features = ["auto-initialize", "extension-module"] }
[[example]]
name = "decorator"
diff --git a/examples/decorator/.template/pre-script.rhai b/examples/decorator/.template/pre-script.rhai
index d8f836a0..cc4a2467 100644
--- a/examples/decorator/.template/pre-script.rhai
+++ b/examples/decorator/.template/pre-script.rhai
@@ -1,4 +1,4 @@
-variable::set("PYO3_VERSION", "0.15.1");
+variable::set("PYO3_VERSION", "0.16.2");
file::rename(".template/Cargo.toml", "Cargo.toml");
file::rename(".template/pyproject.toml", "pyproject.toml");
file::rename(".template/tox.ini", "tox.ini");
diff --git a/examples/decorator/src/lib.rs b/examples/decorator/src/lib.rs
index 35520a17..078b33f0 100644
--- a/examples/decorator/src/lib.rs
+++ b/examples/decorator/src/lib.rs
@@ -29,7 +29,7 @@ impl PyCounter {
#[args(args = "*", kwargs = "**")]
fn __call__(
&mut self,
- py: Python,
+ py: Python<'_>,
args: &PyTuple,
kwargs: Option<&PyDict>,
) -> PyResult> {
@@ -48,7 +48,7 @@ impl PyCounter {
}
#[pymodule]
-pub fn decorator(_py: Python, module: &PyModule) -> PyResult<()> {
+pub fn decorator(_py: Python<'_>, module: &PyModule) -> PyResult<()> {
module.add_class::()?;
Ok(())
}
diff --git a/examples/maturin-starter/.template/pre-script.rhai b/examples/maturin-starter/.template/pre-script.rhai
index d8f836a0..cc4a2467 100644
--- a/examples/maturin-starter/.template/pre-script.rhai
+++ b/examples/maturin-starter/.template/pre-script.rhai
@@ -1,4 +1,4 @@
-variable::set("PYO3_VERSION", "0.15.1");
+variable::set("PYO3_VERSION", "0.16.2");
file::rename(".template/Cargo.toml", "Cargo.toml");
file::rename(".template/pyproject.toml", "pyproject.toml");
file::rename(".template/tox.ini", "tox.ini");
diff --git a/examples/maturin-starter/src/lib.rs b/examples/maturin-starter/src/lib.rs
index 3a79ca51..0b845842 100644
--- a/examples/maturin-starter/src/lib.rs
+++ b/examples/maturin-starter/src/lib.rs
@@ -21,7 +21,7 @@ impl ExampleClass {
/// An example module implemented in Rust using PyO3.
#[pymodule]
-fn maturin_starter(py: Python, m: &PyModule) -> PyResult<()> {
+fn maturin_starter(py: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add_class::()?;
m.add_wrapped(wrap_pymodule!(submodule))?;
diff --git a/examples/maturin-starter/src/submodule.rs b/examples/maturin-starter/src/submodule.rs
index 8096e337..56540b2e 100644
--- a/examples/maturin-starter/src/submodule.rs
+++ b/examples/maturin-starter/src/submodule.rs
@@ -16,7 +16,7 @@ impl SubmoduleClass {
}
#[pymodule]
-pub fn submodule(_py: Python, m: &PyModule) -> PyResult<()> {
+pub fn submodule(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add_class::()?;
Ok(())
}
diff --git a/examples/setuptools-rust-starter/.template/pre-script.rhai b/examples/setuptools-rust-starter/.template/pre-script.rhai
index 2f8d2ee3..9ff84f49 100644
--- a/examples/setuptools-rust-starter/.template/pre-script.rhai
+++ b/examples/setuptools-rust-starter/.template/pre-script.rhai
@@ -1,4 +1,4 @@
-variable::set("PYO3_VERSION", "0.15.1");
+variable::set("PYO3_VERSION", "0.16.2");
file::rename(".template/Cargo.toml", "Cargo.toml");
file::rename(".template/setup.cfg", "setup.cfg");
file::rename(".template/tox.ini", "tox.ini");
diff --git a/examples/setuptools-rust-starter/src/lib.rs b/examples/setuptools-rust-starter/src/lib.rs
index 6f09f43a..46af25f1 100644
--- a/examples/setuptools-rust-starter/src/lib.rs
+++ b/examples/setuptools-rust-starter/src/lib.rs
@@ -21,7 +21,7 @@ impl ExampleClass {
/// An example module implemented in Rust using PyO3.
#[pymodule]
-fn _setuptools_rust_starter(py: Python, m: &PyModule) -> PyResult<()> {
+fn _setuptools_rust_starter(py: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add_class::()?;
m.add_wrapped(wrap_pymodule!(submodule))?;
diff --git a/examples/setuptools-rust-starter/src/submodule.rs b/examples/setuptools-rust-starter/src/submodule.rs
index 8096e337..56540b2e 100644
--- a/examples/setuptools-rust-starter/src/submodule.rs
+++ b/examples/setuptools-rust-starter/src/submodule.rs
@@ -16,7 +16,7 @@ impl SubmoduleClass {
}
#[pymodule]
-pub fn submodule(_py: Python, m: &PyModule) -> PyResult<()> {
+pub fn submodule(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add_class::()?;
Ok(())
}
diff --git a/examples/word-count/.template/pre-script.rhai b/examples/word-count/.template/pre-script.rhai
index 3f0ab336..35864ec3 100644
--- a/examples/word-count/.template/pre-script.rhai
+++ b/examples/word-count/.template/pre-script.rhai
@@ -1,4 +1,4 @@
-variable::set("PYO3_VERSION", "0.15.1");
+variable::set("PYO3_VERSION", "0.16.2");
file::rename(".template/Cargo.toml", "Cargo.toml");
file::rename(".template/tox.ini", "tox.ini");
file::delete(".template");
diff --git a/examples/word-count/src/lib.rs b/examples/word-count/src/lib.rs
index 96f9f9e2..b7d3a803 100644
--- a/examples/word-count/src/lib.rs
+++ b/examples/word-count/src/lib.rs
@@ -17,7 +17,7 @@ fn search_sequential(contents: &str, needle: &str) -> usize {
}
#[pyfunction]
-fn search_sequential_allow_threads(py: Python, contents: &str, needle: &str) -> usize {
+fn search_sequential_allow_threads(py: Python<'_>, contents: &str, needle: &str) -> usize {
py.allow_threads(|| search_sequential(contents, needle))
}
diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md
index 0d5e4b1f..c1ed4711 100644
--- a/guide/src/SUMMARY.md
+++ b/guide/src/SUMMARY.md
@@ -8,6 +8,8 @@
- [Python Functions](function.md)
- [Python Classes](class.md)
- [Class customizations](class/protocols.md)
+ - [Basic object customization](class/object.md)
+ - [Emulating numeric types](class/numeric.md)
- [Emulating callable objects](class/call.md)
- [Type Conversions](conversions.md)
- [Mapping of Rust types to Python types](conversions/tables.md)]
diff --git a/guide/src/building_and_distribution/multiple_python_versions.md b/guide/src/building_and_distribution/multiple_python_versions.md
index ff442072..e7bdd986 100644
--- a/guide/src/building_and_distribution/multiple_python_versions.md
+++ b/guide/src/building_and_distribution/multiple_python_versions.md
@@ -53,31 +53,31 @@ After these steps you are ready to annotate your code!
The `#[cfg]` flags added by `pyo3-build-cfg` can be combined with all of Rust's logic in the `#[cfg]` attribute to create very precise conditional code generation. The following are some common patterns implemented using these flags:
-```
+```rust,ignore
#[cfg(Py_3_7)]
```
This `#[cfg]` marks code that will only be present on Python 3.7 and upwards. There are similar options `Py_3_8`, `Py_3_9`, `Py_3_10` and so on for each minor version.
-```
+```rust,ignore
#[cfg(not(Py_3_7))]
```
This `#[cfg]` marks code that will only be present on Python versions before (but not including) Python 3.7.
-```
+```rust,ignore
#[cfg(not(Py_LIMITED_API))]
```
This `#[cfg]` marks code that is only available when building for the unlimited Python API (i.e. PyO3's `abi3` feature is not enabled). This might be useful if you want to ship your extension module as an `abi3` wheel and also allow users to compile it from source to make use of optimizations only possible with the unlimited API.
-```
+```rust,ignore
#[cfg(any(Py_3_9, not(Py_LIMITED_API)))]
```
This `#[cfg]` marks code which is available when running Python 3.9 or newer, or when using the unlimited API with an older Python version. Patterns like this are commonly seen on Python APIs which were added to the limited Python API in a specific minor version.
-```
+```rust,ignore
#[cfg(PyPy)]
```
diff --git a/guide/src/class.md b/guide/src/class.md
index 1156ae1c..b590b4f9 100644
--- a/guide/src/class.md
+++ b/guide/src/class.md
@@ -2,7 +2,7 @@
PyO3 exposes a group of attributes powered by Rust's proc macro system for defining Python classes as Rust structs.
-The main attribute is `#[pyclass]`, which is placed upon a Rust `struct` or a fieldless `enum` (a.k.a. C-like enum) to generate a Python type for it. They will usually also have *one* `#[pymethods]`-annotated `impl` block for the struct, which is used to define Python methods and constants for the generated Python type. (If the [`multiple-pymethods`] feature is enabled each `#[pyclass]` is allowed to have multiple `#[pymethods]` blocks.) Finally, there may be multiple `#[pyproto]` trait implementations for the struct, which are used to define certain python magic methods such as `__str__`.
+The main attribute is `#[pyclass]`, which is placed upon a Rust `struct` or a fieldless `enum` (a.k.a. C-like enum) to generate a Python type for it. They will usually also have *one* `#[pymethods]`-annotated `impl` block for the struct, which is used to define Python methods and constants for the generated Python type. (If the [`multiple-pymethods`] feature is enabled each `#[pyclass]` is allowed to have multiple `#[pymethods]` blocks.) `#[pymethods]` may also have implementations for Python magic methods such as `__str__`.
This chapter will discuss the functionality and configuration these attributes offer. Below is a list of links to the relevant section of this chapter for each:
@@ -16,18 +16,31 @@ This chapter will discuss the functionality and configuration these attributes o
- [`#[classmethod]`](#class-methods)
- [`#[classattr]`](#class-attributes)
- [`#[args]`](#method-arguments)
-- [`#[pyproto]`](class/protocols.html)
+- [Magic methods and slots](class/protocols.html)
## Defining a new class
To define a custom Python class, add the `#[pyclass]` attribute to a Rust struct or a fieldless enum.
```rust
# #![allow(dead_code)]
-# use pyo3::prelude::*;
+use pyo3::prelude::*;
+
#[pyclass]
-struct MyClass {
- # #[pyo3(get)]
- num: i32,
+struct Integer{
+ inner: i32
+}
+
+// A "tuple" struct
+#[pyclass]
+struct Number(i32);
+
+// PyO3 supports custom discriminants in enums
+#[pyclass]
+enum HttpResponse {
+ Ok = 200,
+ NotFound = 404,
+ Teapot = 418,
+ // ...
}
#[pyclass]
@@ -41,20 +54,67 @@ Because Python objects are freely shared between threads by the Python interpret
The above example generates implementations for [`PyTypeInfo`], [`PyTypeObject`], and [`PyClass`] for `MyClass` and `MyEnum`. To see these generated implementations, refer to the [implementation details](#implementation-details) at the end of this chapter.
-## Adding the class to a module
+## Constructor
-Custom Python classes can then be added to a module using `add_class()`.
+By default it is not possible to create an instance of a custom class from Python code.
+To declare a constructor, you need to define a method and annotate it with the `#[new]`
+attribute. Only Python's `__new__` method can be specified, `__init__` is not available.
```rust
# use pyo3::prelude::*;
# #[pyclass]
-# struct MyClass {
-# #[allow(dead_code)]
-# num: i32,
-# }
+# struct Number(i32);
+#
+#[pymethods]
+impl Number {
+ #[new]
+ fn new(value: i32) -> Self {
+ Number(value)
+ }
+}
+```
+
+Alternatively, if your `new` method may fail you can return `PyResult`.
+
+```rust
+# use pyo3::prelude::*;
+# use pyo3::exceptions::PyValueError;
+# #[pyclass]
+# struct Nonzero(i32);
+#
+#[pymethods]
+impl Nonzero {
+ #[new]
+ fn py_new(value: i32) -> PyResult {
+ if value == 0 {
+ Err(PyValueError::new_err("cannot be zero"))
+ } else {
+ Ok(Nonzero(value))
+ }
+ }
+}
+```
+
+As you can see, the Rust method name is not important here; this way you can
+still use `new()` for a Rust-level constructor.
+
+If no method marked with `#[new]` is declared, object instances can only be
+created from Rust, but not from Python.
+
+For arguments, see the `Method arguments` section below.
+
+## Adding the class to a module
+
+The next step is to create the module initializer and add our class to it
+
+```rust
+# use pyo3::prelude::*;
+# #[pyclass]
+# struct Number(i32);
+#
#[pymodule]
-fn mymodule(_py: Python, m: &PyModule) -> PyResult<()> {
- m.add_class::()?;
+fn my_module(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
+ m.add_class::()?;
Ok(())
}
```
@@ -135,68 +195,16 @@ Python::with_gil(|py|{
## Customizing the class
-The `#[pyclass]` macro accepts the following parameters:
+{{#include ../../pyo3-macros/docs/pyclass_parameters.md}}
-* `name="XXX"` - Set the class name shown in Python code. By default, the struct name is used as the class name.
-* `freelist=XXX` - The `freelist` parameter adds support of free allocation list to custom class.
-The performance improvement applies to types that are often created and deleted in a row,
-so that they can benefit from a freelist. `XXX` is a number of items for the free list.
-* `gc` - Classes with the `gc` parameter participate in Python garbage collection.
-If a custom class contains references to other Python objects that can be collected, the [`PyGCProtocol`]({{#PYO3_DOCS_URL}}/pyo3/class/gc/trait.PyGCProtocol.html) trait has to be implemented.
-* `weakref` - Adds support for Python weak references.
-* `extends=BaseType` - Use a custom base class. The base `BaseType` must implement `PyTypeInfo`. `enum` pyclasses can't use a custom base class.
-* `subclass` - Allows Python classes to inherit from this class. `enum` pyclasses can't be inherited from.
-* `dict` - Adds `__dict__` support, so that the instances of this type have a dictionary containing arbitrary instance variables.
-* `unsendable` - Making it safe to expose `!Send` structs to Python, where all object can be accessed
- by multiple threads. A class marked with `unsendable` panics when accessed by another thread.
-* `module="XXX"` - Set the name of the module the class will be shown as defined in. If not given, the class
- will be a virtual member of the `builtins` module.
+[params-1]: {{#PYO3_DOCS_URL}}/pyo3/prelude/struct.PyAny.html
+[params-2]: https://en.wikipedia.org/wiki/Free_list
+[params-3]: https://doc.rust-lang.org/stable/std/marker/trait.Send.html
+[params-4]: https://doc.rust-lang.org/stable/std/rc/struct.Rc.html
+[params-5]: https://doc.rust-lang.org/stable/std/sync/struct.Rc.html
+[params-6]: https://docs.python.org/3/library/weakref.html
-## Constructor
-
-By default it is not possible to create an instance of a custom class from Python code.
-To declare a constructor, you need to define a method and annotate it with the `#[new]`
-attribute. Only Python's `__new__` method can be specified, `__init__` is not available.
-
-```rust
-# use pyo3::prelude::*;
-#[pyclass]
-struct MyClass {
- # #[allow(dead_code)]
- num: i32,
-}
-
-#[pymethods]
-impl MyClass {
- #[new]
- fn new(num: i32) -> Self {
- MyClass { num }
- }
-}
-```
-
-Alternatively, if your `new` method may fail you can return `PyResult`.
-```rust
-# use pyo3::prelude::*;
-#[pyclass]
-struct MyClass {
- # #[allow(dead_code)]
- num: i32,
-}
-
-#[pymethods]
-impl MyClass {
- #[new]
- fn new(num: i32) -> PyResult {
- Ok(MyClass { num })
- }
-}
-```
-
-If no method marked with `#[new]` is declared, object instances can only be
-created from Rust, but not from Python.
-
-For arguments, see the `Method arguments` section below.
+These parameters are covered in various sections of this guide.
### Return type
@@ -259,7 +267,7 @@ impl SubClass {
(SubClass { val2: 15 }, BaseClass::new())
}
- fn method2(self_: PyRef) -> PyResult {
+ fn method2(self_: PyRef<'_, Self>) -> PyResult {
let super_ = self_.as_ref(); // Get &BaseClass
super_.method().map(|x| x * self_.val2)
}
@@ -278,9 +286,9 @@ impl SubSubClass {
.add_subclass(SubSubClass{val3: 20})
}
- fn method3(self_: PyRef) -> PyResult {
+ fn method3(self_: PyRef<'_, Self>) -> PyResult {
let v = self_.val3;
- let super_ = self_.into_super(); // Get PyRef
+ let super_ = self_.into_super(); // Get PyRef<'_, SubClass>
SubClass::method2(super_).map(|x| x * v)
}
}
@@ -315,7 +323,7 @@ impl DictWithCounter {
fn new() -> Self {
Self::default()
}
- fn set(mut self_: PyRefMut, key: String, value: &PyAny) -> PyResult<()> {
+ fn set(mut self_: PyRefMut<'_, Self>, key: String, value: &PyAny) -> PyResult<()> {
self_.counter.entry(key.clone()).or_insert(0);
let py = self_.py();
let dict: &PyDict = unsafe { py.from_borrowed_ptr_or_err(self_.as_ptr())? };
@@ -510,7 +518,7 @@ gets injected by the method wrapper, e.g.
# }
#[pymethods]
impl MyClass {
- fn method2(&self, py: Python) -> PyResult {
+ fn method2(&self, py: Python<'_>) -> PyResult {
Ok(10)
}
}
@@ -702,7 +710,7 @@ num=-1
## Making class method signatures available to Python
-The [`#[pyo3(text_signature = "...")]`](./function.md#text_signature) option for `#[pyfunction]` also works for classes and methods:
+The [`text_signature = "..."`](./function.md#text_signature) option for `#[pyfunction]` also works for classes and methods:
```rust
# #![allow(dead_code)]
@@ -710,8 +718,7 @@ use pyo3::prelude::*;
use pyo3::types::PyType;
// it works even if the item is not documented:
-#[pyclass]
-#[pyo3(text_signature = "(c, d, /)")]
+#[pyclass(text_signature = "(c, d, /)")]
struct MyClass {}
#[pymethods]
@@ -920,9 +927,9 @@ enum BadSubclass{
## Implementation details
-The `#[pyclass]` macros rely on a lot of conditional code generation: each `#[pyclass]` can optionally have a `#[pymethods]` block as well as several different possible `#[pyproto]` trait implementations.
+The `#[pyclass]` macros rely on a lot of conditional code generation: each `#[pyclass]` can optionally have a `#[pymethods]` block.
-To support this flexibility the `#[pyclass]` macro expands to a blob of boilerplate code which sets up the structure for ["dtolnay specialization"](https://github.com/dtolnay/case-studies/blob/master/autoref-specialization/README.md). This implementation pattern enables the Rust compiler to use `#[pymethods]` and `#[pyproto]` implementations when they are present, and fall back to default (empty) definitions when they are not.
+To support this flexibility the `#[pyclass]` macro expands to a blob of boilerplate code which sets up the structure for ["dtolnay specialization"](https://github.com/dtolnay/case-studies/blob/master/autoref-specialization/README.md). This implementation pattern enables the Rust compiler to use `#[pymethods]` implementations when they are present, and fall back to default (empty) definitions when they are not.
This simple technique works for the case when there is zero or one implementations. To support multiple `#[pymethods]` for a `#[pyclass]` (in the [`multiple-pymethods`] feature), a registry mechanism provided by the [`inventory`](https://github.com/dtolnay/inventory) crate is used instead. This collects `impl`s at library load time, but isn't supported on all platforms. See [inventory: how it works](https://github.com/dtolnay/inventory#how-it-works) for more details.
@@ -941,8 +948,8 @@ unsafe impl ::pyo3::type_object::PyTypeInfo for MyClass {
const NAME: &'static str = "MyClass";
const MODULE: ::std::option::Option<&'static str> = ::std::option::Option::None;
#[inline]
- fn type_object_raw(py: ::pyo3::Python<'_>) -> *mut ::pyo3::ffi::PyTypeObject {
- use ::pyo3::type_object::LazyStaticType;
+ fn type_object_raw(py: pyo3::Python<'_>) -> *mut pyo3::ffi::PyTypeObject {
+ use pyo3::type_object::LazyStaticType;
static TYPE_OBJECT: LazyStaticType = LazyStaticType::new();
TYPE_OBJECT.get_or_init::(py)
}
@@ -964,15 +971,14 @@ impl<'a> ::pyo3::derive_utils::ExtractExt<'a> for &'a MyClass {
type Target = ::pyo3::PyRef<'a, MyClass>;
}
-impl ::pyo3::IntoPy<::pyo3::PyObject> for MyClass {
- fn into_py(self, py: ::pyo3::Python) -> ::pyo3::PyObject {
- ::pyo3::IntoPy::into_py(::pyo3::Py::new(py, self).unwrap(), py)
+impl pyo3::IntoPy for MyClass {
+ fn into_py(self, py: pyo3::Python<'_>) -> pyo3::PyObject {
+ pyo3::IntoPy::into_py(pyo3::Py::new(py, self).unwrap(), py)
}
}
impl pyo3::impl_::pyclass::PyClassImpl for MyClass {
const DOC: &'static str = "Class for demonstration\u{0}";
- const IS_GC: bool = false;
const IS_BASETYPE: bool = false;
const IS_SUBCLASS: bool = false;
type Layout = PyCell;
@@ -987,21 +993,6 @@ impl pyo3::impl_::pyclass::PyClassImpl for MyClass {
visitor(&INTRINSIC_ITEMS);
visitor(collector.py_methods());
}
- fn get_new() -> Option {
- use pyo3::impl_::pyclass::*;
- let collector = PyClassImplCollector::::new();
- collector.new_impl()
- }
- fn get_alloc() -> Option {
- use pyo3::impl_::pyclass::*;
- let collector = PyClassImplCollector::::new();
- collector.alloc_impl()
- }
- fn get_free() -> Option {
- use pyo3::impl_::pyclass::*;
- let collector = PyClassImplCollector::::new();
- collector.free_impl()
- }
}
impl ::pyo3::impl_::pyclass::PyClassDescriptors
diff --git a/guide/src/class/call.md b/guide/src/class/call.md
index c9fe1217..9a470373 100644
--- a/guide/src/class/call.md
+++ b/guide/src/class/call.md
@@ -14,7 +14,7 @@ is linked at the end.
An example crate containing this pyclass can be found [here](https://github.com/PyO3/pyo3/tree/main/examples/decorator)
-```rust
+```rust,ignore
{{#include ../../../examples/decorator/src/lib.rs}}
```
diff --git a/guide/src/class/numeric.md b/guide/src/class/numeric.md
new file mode 100644
index 00000000..a93d2239
--- /dev/null
+++ b/guide/src/class/numeric.md
@@ -0,0 +1,449 @@
+# Emulating numeric types
+
+At this point we have a `Number` class that we can't actually do any math on!
+
+Before proceeding, we should think about how we want to handle overflows. There are three obvious solutions:
+- We can have infinite precision just like Python's `int`. However that would be quite boring - we'd
+ be reinventing the wheel.
+- We can raise exceptions whenever `Number` overflows, but that makes the API painful to use.
+- We can wrap around the boundary of `i32`. This is the approach we'll take here. To do that we'll just forward to `i32`'s
+ `wrapping_*` methods.
+
+### Fixing our constructor
+
+Let's address the first overflow, in `Number`'s constructor:
+
+```python
+from my_module import Number
+
+n = Number(1 << 1337)
+```
+
+```text
+Traceback (most recent call last):
+ File "example.py", line 3, in
+ n = Number(1 << 1337)
+OverflowError: Python int too large to convert to C long
+```
+
+Instead of relying on the default [`FromPyObject`] extraction to parse arguments, we can specify our
+own extraction function, using the `#[pyo3(from_py_with = "...")]` attribute. Unfortunately PyO3
+doesn't provide a way to wrap Python integers out of the box, but we can do a Python call to mask it
+and cast it to an `i32`.
+
+```rust
+# #![allow(dead_code)]
+use pyo3::prelude::*;
+
+fn wrap(obj: &PyAny) -> Result {
+ let val = obj.call_method1("__and__", (0xFFFFFFFF_u32,))?;
+ let val: u32 = val.extract()?;
+ // 👇 This intentionally overflows!
+ Ok(val as i32)
+}
+```
+We also add documentation, via `///` comments and the `#[pyo3(text_signature = "...")]` attribute, both of which are visible to Python users.
+
+```rust
+# #![allow(dead_code)]
+use pyo3::prelude::*;
+
+fn wrap(obj: &PyAny) -> Result {
+ let val = obj.call_method1("__and__", (0xFFFFFFFF_u32,))?;
+ let val: u32 = val.extract()?;
+ Ok(val as i32)
+}
+
+/// Did you ever hear the tragedy of Darth Signed The Overfloweth? I thought not.
+/// It's not a story C would tell you. It's a Rust legend.
+#[pyclass(module = "my_module")]
+#[pyo3(text_signature = "(int)")]
+struct Number(i32);
+
+#[pymethods]
+impl Number {
+ #[new]
+ fn new(#[pyo3(from_py_with = "wrap")] value: i32) -> Self {
+ Self(value)
+ }
+}
+```
+
+
+With that out of the way, let's implement some operators:
+```rust
+use std::convert::TryInto;
+use pyo3::exceptions::{PyZeroDivisionError, PyValueError};
+
+# use pyo3::prelude::*;
+#
+# #[pyclass]
+# struct Number(i32);
+#
+#[pymethods]
+impl Number {
+ fn __add__(&self, other: &Self) -> Self {
+ Self(self.0.wrapping_add(other.0))
+ }
+
+ fn __sub__(&self, other: &Self) -> Self {
+ Self(self.0.wrapping_sub(other.0))
+ }
+
+ fn __mul__(&self, other: &Self) -> Self {
+ Self(self.0.wrapping_mul(other.0))
+ }
+
+ fn __truediv__(&self, other: &Self) -> PyResult {
+ match self.0.checked_div(other.0) {
+ Some(i) => Ok(Self(i)),
+ None => Err(PyZeroDivisionError::new_err("division by zero")),
+ }
+ }
+
+ fn __floordiv__(&self, other: &Self) -> PyResult {
+ match self.0.checked_div(other.0) {
+ Some(i) => Ok(Self(i)),
+ None => Err(PyZeroDivisionError::new_err("division by zero")),
+ }
+ }
+
+ fn __rshift__(&self, other: &Self) -> PyResult {
+ match other.0.try_into() {
+ Ok(rhs) => Ok(Self(self.0.wrapping_shr(rhs))),
+ Err(_) => Err(PyValueError::new_err("negative shift count")),
+ }
+ }
+
+ fn __lshift__(&self, other: &Self) -> PyResult {
+ match other.0.try_into() {
+ Ok(rhs) => Ok(Self(self.0.wrapping_shl(rhs))),
+ Err(_) => Err(PyValueError::new_err("negative shift count")),
+ }
+ }
+}
+```
+
+### Unary arithmethic operations
+
+```rust
+# use pyo3::prelude::*;
+#
+# #[pyclass]
+# struct Number(i32);
+#
+#[pymethods]
+impl Number {
+ fn __pos__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
+ slf
+ }
+
+ fn __neg__(&self) -> Self {
+ Self(-self.0)
+ }
+
+ fn __abs__(&self) -> Self {
+ Self(self.0.abs())
+ }
+
+ fn __invert__(&self) -> Self {
+ Self(!self.0)
+ }
+}
+```
+
+### Support for the `complex()`, `int()` and `float()` built-in functions.
+
+```rust
+# use pyo3::prelude::*;
+#
+# #[pyclass]
+# struct Number(i32);
+#
+use pyo3::types::PyComplex;
+
+#[pymethods]
+impl Number {
+ fn __int__(&self) -> i32 {
+ self.0
+ }
+
+ fn __float__(&self) -> f64 {
+ self.0 as f64
+ }
+
+ fn __complex__<'py>(&self, py: Python<'py>) -> &'py PyComplex {
+ PyComplex::from_doubles(py, self.0 as f64, 0.0)
+ }
+}
+```
+
+We do not implement the in-place operations like `__iadd__` because we do not wish to mutate `Number`.
+Similarly we're not interested in supporting operations with different types, so we do not implement
+ the reflected operations like `__radd__` either.
+
+Now Python can use our `Number` class:
+
+```python
+from my_module import Number
+
+def hash_djb2(s: str):
+ '''
+ A version of Daniel J. Bernstein's djb2 string hashing algorithm
+ Like many hashing algorithms, it relies on integer wrapping.
+ '''
+
+ n = Number(0)
+ five = Number(5)
+
+ for x in s:
+ n = Number(ord(x)) + ((n << five) - n)
+ return n
+
+assert hash_djb2('l50_50') == Number(-1152549421)
+```
+
+### Final code
+
+```rust
+use std::collections::hash_map::DefaultHasher;
+use std::hash::{Hash, Hasher};
+use std::convert::TryInto;
+
+use pyo3::exceptions::{PyValueError, PyZeroDivisionError};
+use pyo3::prelude::*;
+use pyo3::class::basic::CompareOp;
+use pyo3::types::PyComplex;
+
+fn wrap(obj: &PyAny) -> Result {
+ let val = obj.call_method1("__and__", (0xFFFFFFFF_u32,))?;
+ let val: u32 = val.extract()?;
+ Ok(val as i32)
+}
+/// Did you ever hear the tragedy of Darth Signed The Overfloweth? I thought not.
+/// It's not a story C would tell you. It's a Rust legend.
+#[pyclass(module = "my_module")]
+#[pyo3(text_signature = "(int)")]
+struct Number(i32);
+
+#[pymethods]
+impl Number {
+ #[new]
+ fn new(#[pyo3(from_py_with = "wrap")] value: i32) -> Self {
+ Self(value)
+ }
+
+ fn __repr__(&self) -> String {
+ format!("Number({})", self.0)
+ }
+
+ fn __str__(&self) -> String {
+ self.0.to_string()
+ }
+
+ fn __hash__(&self) -> u64 {
+ let mut hasher = DefaultHasher::new();
+ self.0.hash(&mut hasher);
+ hasher.finish()
+ }
+
+ fn __richcmp__(&self, other: &Self, op: CompareOp) -> PyResult {
+ match op {
+ CompareOp::Lt => Ok(self.0 < other.0),
+ CompareOp::Le => Ok(self.0 <= other.0),
+ CompareOp::Eq => Ok(self.0 == other.0),
+ CompareOp::Ne => Ok(self.0 != other.0),
+ CompareOp::Gt => Ok(self.0 > other.0),
+ CompareOp::Ge => Ok(self.0 >= other.0),
+ }
+ }
+
+ fn __bool__(&self) -> bool {
+ self.0 != 0
+ }
+
+ fn __add__(&self, other: &Self) -> Self {
+ Self(self.0.wrapping_add(other.0))
+ }
+
+ fn __sub__(&self, other: &Self) -> Self {
+ Self(self.0.wrapping_sub(other.0))
+ }
+
+ fn __mul__(&self, other: &Self) -> Self {
+ Self(self.0.wrapping_mul(other.0))
+ }
+
+ fn __truediv__(&self, other: &Self) -> PyResult {
+ match self.0.checked_div(other.0) {
+ Some(i) => Ok(Self(i)),
+ None => Err(PyZeroDivisionError::new_err("division by zero")),
+ }
+ }
+
+ fn __floordiv__(&self, other: &Self) -> PyResult {
+ match self.0.checked_div(other.0) {
+ Some(i) => Ok(Self(i)),
+ None => Err(PyZeroDivisionError::new_err("division by zero")),
+ }
+ }
+
+ fn __rshift__(&self, other: &Self) -> PyResult {
+ match other.0.try_into() {
+ Ok(rhs) => Ok(Self(self.0.wrapping_shr(rhs))),
+ Err(_) => Err(PyValueError::new_err("negative shift count")),
+ }
+ }
+
+ fn __lshift__(&self, other: &Self) -> PyResult {
+ match other.0.try_into() {
+ Ok(rhs) => Ok(Self(self.0.wrapping_shl(rhs))),
+ Err(_) => Err(PyValueError::new_err("negative shift count")),
+ }
+ }
+
+ fn __xor__(&self, other: &Self) -> Self {
+ Self(self.0 ^ other.0)
+ }
+
+ fn __or__(&self, other: &Self) -> Self {
+ Self(self.0 | other.0)
+ }
+
+ fn __and__(&self, other: &Self) -> Self {
+ Self(self.0 & other.0)
+ }
+
+ fn __int__(&self) -> i32 {
+ self.0
+ }
+
+ fn __float__(&self) -> f64 {
+ self.0 as f64
+ }
+
+ fn __complex__<'py>(&self, py: Python<'py>) -> &'py PyComplex {
+ PyComplex::from_doubles(py, self.0 as f64, 0.0)
+ }
+}
+
+#[pymodule]
+fn my_module(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
+ m.add_class::()?;
+ Ok(())
+}
+# const SCRIPT: &'static str = r#"
+# def hash_djb2(s: str):
+# n = Number(0)
+# five = Number(5)
+#
+# for x in s:
+# n = Number(ord(x)) + ((n << five) - n)
+# return n
+#
+# assert hash_djb2('l50_50') == Number(-1152549421)
+# assert hash_djb2('logo') == Number(3327403)
+# assert hash_djb2('horizon') == Number(1097468315)
+#
+#
+# assert Number(2) + Number(2) == Number(4)
+# assert Number(2) + Number(2) != Number(5)
+#
+# assert Number(13) - Number(7) == Number(6)
+# assert Number(13) - Number(-7) == Number(20)
+#
+# assert Number(13) / Number(7) == Number(1)
+# assert Number(13) // Number(7) == Number(1)
+#
+# assert Number(13) * Number(7) == Number(13*7)
+#
+# assert Number(13) > Number(7)
+# assert Number(13) < Number(20)
+# assert Number(13) == Number(13)
+# assert Number(13) >= Number(7)
+# assert Number(13) <= Number(20)
+# assert Number(13) == Number(13)
+#
+#
+# assert (True if Number(1) else False)
+# assert (False if Number(0) else True)
+#
+#
+# assert int(Number(13)) == 13
+# assert float(Number(13)) == 13
+# assert Number.__doc__ == "Did you ever hear the tragedy of Darth Signed The Overfloweth? I thought not.\nIt's not a story C would tell you. It's a Rust legend."
+# assert Number(12345234523452) == Number(1498514748)
+# try:
+# import inspect
+# assert inspect.signature(Number).__str__() == '(int)'
+# except ValueError:
+# # Not supported with `abi3` before Python 3.10
+# pass
+# assert Number(1337).__str__() == '1337'
+# assert Number(1337).__repr__() == 'Number(1337)'
+"#;
+
+#
+# use pyo3::type_object::PyTypeObject;
+#
+# fn main() -> PyResult<()> {
+# Python::with_gil(|py| -> PyResult<()> {
+# let globals = PyModule::import(py, "__main__")?.dict();
+# globals.set_item("Number", Number::type_object(py))?;
+#
+# py.run(SCRIPT, Some(globals), None)?;
+# Ok(())
+# })
+# }
+
+```
+
+## Appendix: Writing some unsafe code
+
+At the beginning of this chapter we said that PyO3 doesn't provide a way to wrap Python integers out
+of the box but that's a half truth. There's not a PyO3 API for it, but there's a Python C API
+function that does:
+
+```c
+unsigned long PyLong_AsUnsignedLongMask(PyObject *obj)
+```
+
+We can call this function from Rust by using [`pyo3::ffi::PyLong_AsUnsignedLongMask`]. This is an *unsafe*
+function, which means we have to use an unsafe block to call it and take responsibility for upholding
+the contracts of this function. Let's review those contracts:
+- The GIL must be held. If it's not, calling this function causes a data race.
+- The pointer must be valid, i.e. it must be properly aligned and point to a valid Python object.
+
+Let's create that helper function. The signature has to be `fn(&PyAny) -> PyResult`.
+- `&PyAny` represents a checked borrowed reference, so the pointer derived from it is valid (and not null).
+- Whenever we have borrowed references to Python objects in scope, it is guaranteed that the GIL is held. This reference is also where we can get a [`Python`] token to use in our call to [`PyErr::take`].
+
+```rust
+# #![allow(dead_code)]
+use std::os::raw::c_ulong;
+use pyo3::prelude::*;
+use pyo3::ffi;
+use pyo3::conversion::AsPyPointer;
+
+fn wrap(obj: &PyAny) -> Result {
+ let py: Python<'_> = obj.py();
+
+ unsafe {
+ let ptr = obj.as_ptr();
+
+ let ret: c_ulong = ffi::PyLong_AsUnsignedLongMask(ptr);
+ if ret == c_ulong::MAX {
+ if let Some(err) = PyErr::take(py) {
+ return Err(err);
+ }
+ }
+
+ Ok(ret as i32)
+ }
+}
+```
+
+[`PyErr::take`]: https://docs.rs/pyo3/latest/pyo3/prelude/struct.PyErr.html#method.take
+[`Python`]: https://docs.rs/pyo3/latest/pyo3/struct.Python.html
+[`FromPyObject`]: https://docs.rs/pyo3/latest/pyo3/conversion/trait.FromPyObject.html
+[`pyo3::ffi::PyLong_AsUnsignedLongMask`]: https://docs.rs/pyo3/latest/pyo3/ffi/fn.PyLong_AsUnsignedLongMask.html
\ No newline at end of file
diff --git a/guide/src/class/object.md b/guide/src/class/object.md
new file mode 100644
index 00000000..ade91da8
--- /dev/null
+++ b/guide/src/class/object.md
@@ -0,0 +1,232 @@
+# Basic object customization
+
+Recall the `Number` class from the previous chapter:
+
+```rust
+use pyo3::prelude::*;
+
+#[pyclass]
+struct Number(i32);
+
+#[pymethods]
+impl Number {
+ #[new]
+ fn new(value: i32) -> Self {
+ Self(value)
+ }
+}
+
+#[pymodule]
+fn my_module(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
+ m.add_class::()?;
+ Ok(())
+}
+```
+
+At this point Python code can import the module, access the class and create class instances - but
+nothing else.
+
+```python
+from my_module import Number
+
+n = Number(5)
+print(n)
+```
+
+```text
+
+```
+
+### String representations
+
+It can't even print an user-readable representation of itself! We can fix that by defining the
+ `__repr__` and `__str__` methods inside a `#[pymethods]` block. We do this by accessing the value
+ contained inside `Number`.
+
+ ```rust
+# use pyo3::prelude::*;
+#
+# #[pyclass]
+# struct Number(i32);
+#
+#[pymethods]
+impl Number {
+ // For `__repr__` we want to return a string that Python code could use to recreate
+ // the `Number`, like `Number(5)` for example.
+ fn __repr__(&self) -> String {
+ // We use the `format!` macro to create a string. Its first argument is a
+ // format string, followed by any number of parameters which replace the
+ // `{}`'s in the format string.
+ //
+ // 👇 Tuple field access in Rust uses a dot
+ format!("Number({})", self.0)
+ }
+
+ // `__str__` is generally used to create an "informal" representation, so we
+ // just forward to `i32`'s `ToString` trait implementation to print a bare number.
+ fn __str__(&self) -> String {
+ self.0.to_string()
+ }
+}
+```
+
+### Hashing
+
+
+Let's also implement hashing. We'll just hash the `i32`. For that we need a [`Hasher`]. The one
+provided by `std` is [`DefaultHasher`], which uses the [SipHash] algorithm.
+
+```rust
+use std::collections::hash_map::DefaultHasher;
+
+// Required to call the `.hash` and `.finish` methods, which are defined on traits.
+use std::hash::{Hash, Hasher};
+
+# use pyo3::prelude::*;
+#
+# #[pyclass]
+# struct Number(i32);
+#
+#[pymethods]
+impl Number {
+ fn __hash__(&self) -> u64 {
+ let mut hasher = DefaultHasher::new();
+ self.0.hash(&mut hasher);
+ hasher.finish()
+ }
+}
+```
+
+> **Note**: When implementing `__hash__` and comparisons, it is important that the following property holds:
+>
+> ```text
+> k1 == k2 -> hash(k1) == hash(k2)
+> ```
+>
+> In other words, if two keys are equal, their hashes must also be equal. In addition you must take
+> care that your classes' hash doesn't change during its lifetime. In this tutorial we do that by not
+> letting Python code change our `Number` class. In other words, it is immutable.
+>
+> By default, all `#[pyclass]` types have a default hash implementation from Python.
+> Types which should not be hashable can override this by setting `__hash__` to None.
+> This is the same mechanism as for a pure-Python class. This is done like so:
+>
+> ```rust
+> # use pyo3::prelude::*;
+> #[pyclass]
+> struct NotHashable { }
+>
+> #[pymethods]
+> impl NotHashable {
+> #[classattr]
+> const __hash__: Option> = None;
+>}
+> ```
+
+### Comparisons
+
+Unlike in Python, PyO3 does not provide the magic comparison methods you might expect like `__eq__`,
+ `__lt__` and so on. Instead you have to implement all six operations at once with `__richcmp__`.
+This method will be called with a value of `CompareOp` depending on the operation.
+
+```rust
+use pyo3::class::basic::CompareOp;
+
+# use pyo3::prelude::*;
+#
+# #[pyclass]
+# struct Number(i32);
+#
+#[pymethods]
+impl Number {
+ fn __richcmp__(&self, other: &Self, op: CompareOp) -> PyResult {
+ match op {
+ CompareOp::Lt => Ok(self.0 < other.0),
+ CompareOp::Le => Ok(self.0 <= other.0),
+ CompareOp::Eq => Ok(self.0 == other.0),
+ CompareOp::Ne => Ok(self.0 != other.0),
+ CompareOp::Gt => Ok(self.0 > other.0),
+ CompareOp::Ge => Ok(self.0 >= other.0),
+ }
+ }
+}
+```
+
+### Truthyness
+
+We'll consider `Number` to be `True` if it is nonzero:
+
+```rust
+# use pyo3::prelude::*;
+#
+# #[pyclass]
+# struct Number(i32);
+#
+#[pymethods]
+impl Number {
+ fn __bool__(&self) -> bool {
+ self.0 != 0
+ }
+}
+```
+
+### Final code
+
+```rust
+use std::collections::hash_map::DefaultHasher;
+use std::hash::{Hash, Hasher};
+
+use pyo3::prelude::*;
+use pyo3::class::basic::CompareOp;
+
+#[pyclass]
+struct Number(i32);
+
+#[pymethods]
+impl Number {
+ #[new]
+ fn new(value: i32) -> Self {
+ Self(value)
+ }
+
+ fn __repr__(&self) -> String {
+ format!("Number({})", self.0)
+ }
+
+ fn __str__(&self) -> String {
+ self.0.to_string()
+ }
+
+ fn __hash__(&self) -> u64 {
+ let mut hasher = DefaultHasher::new();
+ self.0.hash(&mut hasher);
+ hasher.finish()
+ }
+
+ fn __richcmp__(&self, other: &Self, op: CompareOp) -> PyResult {
+ match op {
+ CompareOp::Lt => Ok(self.0 < other.0),
+ CompareOp::Le => Ok(self.0 <= other.0),
+ CompareOp::Eq => Ok(self.0 == other.0),
+ CompareOp::Ne => Ok(self.0 != other.0),
+ CompareOp::Gt => Ok(self.0 > other.0),
+ CompareOp::Ge => Ok(self.0 >= other.0),
+ }
+ }
+
+ fn __bool__(&self) -> bool {
+ self.0 != 0
+ }
+}
+
+#[pymodule]
+fn my_module(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
+ m.add_class::()?;
+ Ok(())
+}
+```
+
+[`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html
+[`Hasher`]: https://doc.rust-lang.org/std/hash/trait.Hasher.html
+[`DefaultHasher`]: https://doc.rust-lang.org/std/collections/hash_map/struct.DefaultHasher.html
+[SipHash]: https://en.wikipedia.org/wiki/SipHash
\ No newline at end of file
diff --git a/guide/src/class/protocols.md b/guide/src/class/protocols.md
index d0d570de..320307d0 100644
--- a/guide/src/class/protocols.md
+++ b/guide/src/class/protocols.md
@@ -1,19 +1,15 @@
-# Class customizations
+# Magic methods and slots
Python's object model defines several protocols for different object behavior, such as the sequence, mapping, and number protocols. You may be familiar with implementing these protocols in Python classes by "magic" methods, such as `__str__` or `__repr__`. Because of the double-underscores surrounding their name, these are also known as "dunder" methods.
In the Python C-API which PyO3 is implemented upon, many of these magic methods have to be placed into special "slots" on the class type object, as covered in the previous section. There are two ways in which this can be done:
- - [Experimental for PyO3 0.15, may change slightly in PyO3 0.16] In `#[pymethods]`, if the name of the method is a recognised magic method, PyO3 will place it in the type object automatically.
- - [Stable, but expected to be deprecated in PyO3 0.16] In special traits combined with the `#[pyproto]` attribute.
+ - [New in PyO3 0.15, recommended in PyO3 0.16] In `#[pymethods]`, if the name of the method is a recognised magic method, PyO3 will place it in the type object automatically.
+ - [Deprecated in PyO3 0.16] In special traits combined with the `#[pyproto]` attribute.
(There are also many magic methods which don't have a special slot, such as `__dir__`. These methods can be implemented as normal in `#[pymethods]`.)
-This chapter gives a brief overview of the available methods. An in depth example is given in the following sub-chapters.
-
-### Magic methods in `#[pymethods]`
-
-In PyO3 0.15, if a function name in `#[pymethods]` is a recognised magic method, it will be automatically placed into the correct slot in the Python type object. The function name is taken from the usual rules for naming `#[pymethods]`: the `#[pyo3(name = "...")]` attribute is used if present, otherwise the Rust function name is used.
+If a function name in `#[pymethods]` is a recognised magic method, it will be automatically placed into the correct slot in the Python type object. The function name is taken from the usual rules for naming `#[pymethods]`: the `#[pyo3(name = "...")]` attribute is used if present, otherwise the Rust function name is used.
The magic methods handled by PyO3 are very similar to the standard Python ones on [this page](https://docs.python.org/3/reference/datamodel.html#special-method-names) - in particular they are the the subset which have slots as [defined here](https://docs.python.org/3/c-api/typeobj.html). Some of the slots do not have a magic method in Python, which leads to a few additional magic methods defined only in PyO3:
- Magic methods for garbage collection
@@ -26,8 +22,8 @@ When PyO3 handles a magic method, a couple of changes apply compared to other `#
The following sections list of all magic methods PyO3 currently handles. The
given signatures should be interpreted as follows:
- All methods take a receiver as first argument, shown as ``. It can be
- `&self`, `&mut self` or a `PyCell` reference like `self_: PyRef` and
- `self_: PyRefMut`, as described [here](../class.md#inheritance).
+ `&self`, `&mut self` or a `PyCell` reference like `self_: PyRef<'_, Self>` and
+ `self_: PyRefMut<'_, Self>`, as described [here](../class.md#inheritance).
- An optional `Python<'py>` argument is always allowed as the first argument.
- Return values can be optionally wrapped in `PyResult`.
- `object` means that any type is allowed that can be extracted from a Python
@@ -41,14 +37,16 @@ given signatures should be interpreted as follows:
string object. This is indicated by `object (Python type)`.
-#### Basic object customization
+### Basic object customization
- `__str__() -> object (str)`
- `__repr__() -> object (str)`
+
- `__hash__() -> isize`
+
+ Objects that compare equal must have the same hash value.
Disabling Python's default hash
-
By default, all `#[pyclass]` types have a default hash implementation from Python. Types which should not be hashable can override this by setting `__hash__` to `None`. This is the same mechanism as for a pure-Python class. This is done like so:
```rust
@@ -66,33 +64,152 @@ given signatures should be interpreted as follows:
- `__richcmp__(, object, pyo3::basic::CompareOp) -> object`
+
+ Overloads Python comparison operations (`==`, `!=`, `<`, `<=`, `>`, and `>=`).
+ The `CompareOp` argument indicates the comparison operation being performed.
+
+ Return type
+ The return type will normally be `PyResult`, but any Python object can be returned.
+ If the `object` is not of the type specified in the signature, the generated code will
+ automatically `return NotImplemented`.
+
+
- `__getattr__(, object) -> object`
- - `__setattr__(, object, object) -> ()`
+ - `__getattribute__(, object) -> object`
+
+ Differences between `__getattr__` and `__getattribute__`
+ As in Python, `__getattr__` is only called if the attribute is not found
+ by normal attribute lookup. `__getattribute__`, on the other hand, is
+ called for *every* attribute access. If it wants to access existing
+ attributes on `self`, it needs to be very careful not to introduce
+ infinite recursion, and use `baseclass.__getattribute__()`.
+
+
+ - `__setattr__(, value: object) -> ()`
- `__delattr__(, object) -> ()`
+
+ Overrides attribute access.
+
- `__bool__() -> bool`
+ Determines the "truthyness" of an object.
+
- `__call__(, ...) -> object` - here, any argument list can be defined
as for normal `pymethods`
-#### Iterable objects
+### Iterable objects
+
+Iterators can be defined using these methods:
- `__iter__() -> object`
- `__next__() -> Option