Merge branch 'main' of https://github.com/mejrs/pyo3 into immutable
This commit is contained in:
commit
086424b88c
|
@ -1,8 +1,21 @@
|
|||
[alias]
|
||||
xtask = "run --package xtask --"
|
||||
pyo3_doc = "doc --lib --no-default-features --features=full --no-deps --workspace --open --exclude pyo3-macros --exclude pyo3-macros-backend"
|
||||
pyo3_doc_scrape = "doc --lib --no-default-features --features=full --no-deps --workspace --open --exclude pyo3-macros --exclude pyo3-macros-backend -Z unstable-options -Z rustdoc-scrape-examples=examples"
|
||||
pyo3_doc_internal = "doc --lib --no-default-features --features=full --no-deps --workspace --open --document-private-items -Z unstable-options -Z rustdoc-scrape-examples=examples"
|
||||
|
||||
[build]
|
||||
rustdocflags = ["--cfg", "docsrs"]
|
||||
[target.'cfg(feature = "cargo-clippy")']
|
||||
rustflags = [
|
||||
"-Dclippy::checked_conversions",
|
||||
"-Dclippy::dbg_macro",
|
||||
"-Dclippy::explicit_into_iter_loop",
|
||||
"-Dclippy::explicit_iter_loop",
|
||||
"-Dclippy::filter_map_next",
|
||||
"-Dclippy::flat_map_option",
|
||||
"-Dclippy::let_unit_value",
|
||||
"-Dclippy::manual_assert",
|
||||
"-Dclippy::manual_ok_or",
|
||||
"-Dclippy::todo",
|
||||
"-Dclippy::unnecessary_wraps",
|
||||
"-Dclippy::useless_transmute",
|
||||
"-Delided_lifetimes_in_paths",
|
||||
"-Dunused_lifetimes",
|
||||
"-Drust_2021_prelude_collisions"
|
||||
]
|
|
@ -5,11 +5,6 @@ Please consider adding the following to your pull request:
|
|||
- docs to all new functions and / or detail in the guide
|
||||
- tests for all new or changed functions
|
||||
|
||||
Be aware the CI pipeline will check your pull request for the following:
|
||||
- Rust tests (Just `cargo test` or `make test` if you need to test examples)
|
||||
- Rust lints (`make clippy`)
|
||||
- Rust formatting (`cargo fmt`)
|
||||
- Python formatting (`black . --check`. You can install black with `pip install black`)
|
||||
- Compatibility with all supported Python versions for all examples. This uses `tox`; you can do run it using `cargo xtask test-py`.
|
||||
|
||||
You can run a similar set of checks as the CI pipeline using `make test`.
|
||||
PyO3's CI pipeline will check your pull request. To run its tests
|
||||
locally, you can run ```cargo xtask ci```. See its documentation
|
||||
[here](https://github.com/PyO3/pyo3/tree/main/xtask#readme).
|
||||
|
|
|
@ -1,95 +0,0 @@
|
|||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
name: Benchmark
|
||||
|
||||
jobs:
|
||||
benchmark:
|
||||
name: Cargo benchmark
|
||||
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: rhysd/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:
|
||||
name: pytest 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: rhysd/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' }}
|
|
@ -6,6 +6,10 @@ on:
|
|||
- main
|
||||
pull_request:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
|
@ -15,33 +19,36 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2
|
||||
- run: pip install black==21.12b0
|
||||
- run: pip install nox
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
components: rustfmt
|
||||
- name: Check python formatting (black)
|
||||
run: make fmt_py
|
||||
run: nox -s fmt-py
|
||||
- name: Check rust formatting (rustfmt)
|
||||
run: make fmt_rust
|
||||
run: nox -s fmt-rust
|
||||
|
||||
clippy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2
|
||||
- run: pip install nox
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
components: clippy
|
||||
- run: make clippy
|
||||
- run: nox -s clippy
|
||||
|
||||
check-target:
|
||||
needs: [fmt]
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false # If one platform fails, allow the rest to keep testing.
|
||||
# If one platform fails, allow the rest to keep testing if `CI-no-fail-fast` label is present
|
||||
fail-fast: ${{ !contains(github.event.pull_request.labels.*.name, 'CI-no-fail-fast') }}
|
||||
matrix:
|
||||
target: [powerpc64le-unknown-linux-gnu, s390x-unknown-linux-gnu, wasm32-wasi]
|
||||
name: check-${{ matrix.target }}
|
||||
|
@ -70,13 +77,23 @@ jobs:
|
|||
|
||||
build:
|
||||
needs: [fmt] # don't wait for clippy as fails rarely and takes longer
|
||||
name: python${{ matrix.python-version }}-${{ matrix.platform.python-architecture }} ${{ matrix.platform.os }} ${{ matrix.msrv }}
|
||||
name: python${{ matrix.python-version }}-${{ matrix.platform.python-architecture }} ${{ matrix.platform.os }} rust-${{ matrix.rust }}
|
||||
runs-on: ${{ matrix.platform.os }}
|
||||
strategy:
|
||||
fail-fast: false # If one platform fails, allow the rest to keep testing.
|
||||
# If one platform fails, allow the rest to keep testing if `CI-no-fail-fast` label is present
|
||||
fail-fast: ${{ !contains(github.event.pull_request.labels.*.name, 'CI-no-fail-fast') }}
|
||||
matrix:
|
||||
rust: [stable]
|
||||
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11-dev", "pypy-3.7", "pypy-3.8"]
|
||||
python-version: [
|
||||
"3.7",
|
||||
"3.8",
|
||||
"3.9",
|
||||
"3.10",
|
||||
"3.11-dev",
|
||||
"pypy-3.7",
|
||||
"pypy-3.8",
|
||||
"pypy-3.9"
|
||||
]
|
||||
platform:
|
||||
[
|
||||
{
|
||||
|
@ -94,18 +111,7 @@ jobs:
|
|||
python-architecture: "x64",
|
||||
rust-target: "x86_64-pc-windows-msvc",
|
||||
},
|
||||
{
|
||||
os: "windows-latest",
|
||||
python-architecture: "x86",
|
||||
rust-target: "i686-pc-windows-msvc",
|
||||
},
|
||||
]
|
||||
exclude:
|
||||
# PyPy doesn't release 32-bit Windows builds any more
|
||||
- python-version: pypy-3.7
|
||||
platform: { os: "windows-latest", python-architecture: "x86" }
|
||||
- python-version: pypy-3.8
|
||||
platform: { os: "windows-latest", python-architecture: "x86" }
|
||||
include:
|
||||
# Test minimal supported Rust version
|
||||
- rust: 1.48.0
|
||||
|
@ -118,6 +124,26 @@ jobs:
|
|||
}
|
||||
msrv: "MSRV"
|
||||
|
||||
# Test the `nightly` feature
|
||||
- rust: nightly
|
||||
python-version: "3.10"
|
||||
platform:
|
||||
{
|
||||
os: "ubuntu-latest",
|
||||
python-architecture: "x64",
|
||||
rust-target: "x86_64-unknown-linux-gnu",
|
||||
}
|
||||
extra_features: "nightly"
|
||||
|
||||
# Test 32-bit Windows only with the latest Python version
|
||||
- rust: stable
|
||||
python-version: "3.10"
|
||||
platform:
|
||||
{
|
||||
os: "windows-latest",
|
||||
python-architecture: "x86",
|
||||
rust-target: "i686-pc-windows-msvc",
|
||||
}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
|
@ -149,11 +175,16 @@ jobs:
|
|||
- if: matrix.msrv == 'MSRV'
|
||||
name: Prepare minimal package versions (MSRV only)
|
||||
run: |
|
||||
set -x
|
||||
cargo update -p indexmap --precise 1.6.2
|
||||
cargo update -p hashbrown:0.11.2 --precise 0.9.1
|
||||
cargo update -p hashbrown:0.12.0 --precise 0.9.1
|
||||
PROJECTS=("." "examples/decorator" "examples/maturin-starter" "examples/setuptools-rust-starter" "examples/word-count")
|
||||
for PROJ in ${PROJECTS[@]}; do
|
||||
cargo update --manifest-path "$PROJ/Cargo.toml" -p parking_lot --precise 0.11.0
|
||||
done
|
||||
|
||||
- name: Build docs
|
||||
run: cargo doc --no-deps --no-default-features --features full
|
||||
run: cargo doc --no-deps --no-default-features --features "full ${{ matrix.extra_features }}"
|
||||
|
||||
- name: Build (no features)
|
||||
run: cargo build --lib --tests --no-default-features
|
||||
|
@ -176,26 +207,26 @@ jobs:
|
|||
cargo test --no-default-features
|
||||
|
||||
- name: Build (all additive features)
|
||||
run: cargo build --lib --tests --no-default-features --features full
|
||||
run: cargo build --lib --tests --no-default-features --features "full ${{ matrix.extra_features }}"
|
||||
|
||||
- if: ${{ startsWith(matrix.python-version, 'pypy') }}
|
||||
name: Build PyPy (abi3-py37)
|
||||
run: cargo build --lib --tests --no-default-features --features "abi3-py37 full"
|
||||
run: cargo build --lib --tests --no-default-features --features "abi3-py37 full ${{ matrix.extra_features }}"
|
||||
|
||||
# Run tests (except on PyPy, because no embedding API).
|
||||
- if: ${{ !startsWith(matrix.python-version, 'pypy') }}
|
||||
name: Test
|
||||
run: cargo test --no-default-features --features full
|
||||
run: cargo test --no-default-features --features "full ${{ matrix.extra_features }}"
|
||||
|
||||
# Run tests again, but in abi3 mode
|
||||
- if: ${{ !startsWith(matrix.python-version, 'pypy') }}
|
||||
name: Test (abi3)
|
||||
run: cargo test --no-default-features --features "abi3 full"
|
||||
run: cargo test --no-default-features --features "abi3 full ${{ matrix.extra_features }}"
|
||||
|
||||
# Run tests again, for abi3-py37 (the minimal Python version)
|
||||
- if: ${{ (!startsWith(matrix.python-version, 'pypy')) && (matrix.python-version != '3.7') }}
|
||||
name: Test (abi3-py37)
|
||||
run: cargo test --no-default-features --features "abi3-py37 full"
|
||||
run: cargo test --no-default-features --features "abi3-py37 full ${{ matrix.extra_features }}"
|
||||
|
||||
- name: Test proc-macro code
|
||||
run: cargo test --manifest-path=pyo3-macros-backend/Cargo.toml
|
||||
|
@ -221,6 +252,25 @@ jobs:
|
|||
manylinux: auto
|
||||
args: --release -i python3.9 --no-sdist -m examples/maturin-starter/Cargo.toml
|
||||
|
||||
- run: sudo rm -rf examples/maturin-starter/target
|
||||
if: ${{ matrix.platform.os == 'ubuntu-latest' && matrix.python-version == '3.9' }}
|
||||
- name: Test cross compile to same architecture
|
||||
if: ${{ matrix.platform.os == 'ubuntu-latest' && matrix.python-version == '3.9' }}
|
||||
uses: messense/maturin-action@v1
|
||||
env:
|
||||
PYO3_CROSS_LIB_DIR: /opt/python/cp39-cp39/lib
|
||||
with:
|
||||
target: x86_64-unknown-linux-gnu
|
||||
manylinux: auto
|
||||
args: --release -i python3.9 --no-sdist -m examples/maturin-starter/Cargo.toml
|
||||
|
||||
- name: Test cross compilation
|
||||
if: ${{ matrix.platform.os == 'macos-latest' && matrix.python-version == '3.9' }}
|
||||
uses: messense/maturin-action@v1
|
||||
with:
|
||||
target: aarch64-apple-darwin
|
||||
args: --release -i python3.9 --no-sdist -m examples/maturin-starter/Cargo.toml
|
||||
|
||||
env:
|
||||
CARGO_TERM_VERBOSE: true
|
||||
CARGO_BUILD_TARGET: ${{ matrix.platform.rust-target }}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
name: guide
|
||||
name: gh-pages
|
||||
|
||||
on:
|
||||
push:
|
||||
|
@ -8,11 +8,15 @@ on:
|
|||
release:
|
||||
types: [published]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
build:
|
||||
guide-build:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
tag_name: ${{ steps.prepare_tag.outputs.tag_name }}
|
||||
|
@ -41,7 +45,7 @@ jobs:
|
|||
mkdir target
|
||||
mkdir -p gh-pages-build/internal
|
||||
echo "<div class='internal-banner' style='position:fixed; z-index: 99999; color:red;border:3px solid red;margin-left: auto; margin-right: auto; width: 430px;left:0;right: 0;'><div style='display: flex; align-items: center; justify-content: center;'> ⚠️ Internal Docs ⚠️ Not Public API 👉 <a href='https://pyo3.rs/main/doc/pyo3/index.html' style='color:red;text-decoration:underline;'>Official Docs Here</a></div></div>" > 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 "<meta http-equiv=refresh content=0;url=pyo3/index.html>" > 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' }}
|
|
@ -20,3 +20,4 @@ guide/book/
|
|||
extensions/stamps/
|
||||
pip-wheel-metadata
|
||||
valgrind-python.supp
|
||||
*.pyd
|
||||
|
|
|
@ -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<T>(slf: *mut PyObject) -> *mut PyObject`.
|
||||
When `#[pyproto]` finds that `T` implements `PyIterProtocol::__iter__`, it automatically
|
||||
sets `iter<T>` 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.
|
||||
|
||||
|
|
99
CHANGELOG.md
99
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<PyBytes>`. [#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<Py<PyType>>` 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<Py<PyType>>` 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<T>`. [#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
|
||||
|
|
30
Cargo.toml
30
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 <https://github.com/PyO3>"]
|
||||
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"
|
||||
|
|
|
@ -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.
|
||||
|
|
41
Makefile
41
Makefile
|
@ -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
|
12
README.md
12
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<String> {
|
|||
/// 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
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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::<u64, u64>::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;
|
||||
|
|
|
@ -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"))
|
||||
}
|
||||
|
||||
|
|
|
@ -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(|| {
|
||||
|
|
|
@ -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(
|
||||
|| {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(|| {
|
||||
|
|
|
@ -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(|| {
|
||||
|
|
|
@ -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::<u64>::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;
|
||||
|
|
|
@ -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;
|
||||
|
|
27
build.rs
27
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<u32> {
|
||||
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(())
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -29,7 +29,7 @@ impl PyCounter {
|
|||
#[args(args = "*", kwargs = "**")]
|
||||
fn __call__(
|
||||
&mut self,
|
||||
py: Python,
|
||||
py: Python<'_>,
|
||||
args: &PyTuple,
|
||||
kwargs: Option<&PyDict>,
|
||||
) -> PyResult<Py<PyAny>> {
|
||||
|
@ -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::<PyCounter>()?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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::<ExampleClass>()?;
|
||||
m.add_wrapped(wrap_pymodule!(submodule))?;
|
||||
|
||||
|
|
|
@ -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::<SubmoduleClass>()?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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::<ExampleClass>()?;
|
||||
m.add_wrapped(wrap_pymodule!(submodule))?;
|
||||
|
||||
|
|
|
@ -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::<SubmoduleClass>()?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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)]
|
||||
```
|
||||
|
||||
|
|
|
@ -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<Self>`.
|
||||
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
# use pyo3::exceptions::PyValueError;
|
||||
# #[pyclass]
|
||||
# struct Nonzero(i32);
|
||||
#
|
||||
#[pymethods]
|
||||
impl Nonzero {
|
||||
#[new]
|
||||
fn py_new(value: i32) -> PyResult<Self> {
|
||||
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::<MyClass>()?;
|
||||
fn my_module(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||
m.add_class::<Number>()?;
|
||||
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<Self>`.
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
#[pyclass]
|
||||
struct MyClass {
|
||||
# #[allow(dead_code)]
|
||||
num: i32,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl MyClass {
|
||||
#[new]
|
||||
fn new(num: i32) -> PyResult<Self> {
|
||||
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<Self>) -> PyResult<usize> {
|
||||
fn method2(self_: PyRef<'_, Self>) -> PyResult<usize> {
|
||||
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<Self>) -> PyResult<usize> {
|
||||
fn method3(self_: PyRef<'_, Self>) -> PyResult<usize> {
|
||||
let v = self_.val3;
|
||||
let super_ = self_.into_super(); // Get PyRef<SubClass>
|
||||
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<Self>, 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<i32> {
|
||||
fn method2(&self, py: Python<'_>) -> PyResult<i32> {
|
||||
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::<Self>(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<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::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<MyClass>;
|
||||
|
@ -987,21 +993,6 @@ impl pyo3::impl_::pyclass::PyClassImpl for MyClass {
|
|||
visitor(&INTRINSIC_ITEMS);
|
||||
visitor(collector.py_methods());
|
||||
}
|
||||
fn get_new() -> Option<pyo3::ffi::newfunc> {
|
||||
use pyo3::impl_::pyclass::*;
|
||||
let collector = PyClassImplCollector::<Self>::new();
|
||||
collector.new_impl()
|
||||
}
|
||||
fn get_alloc() -> Option<pyo3::ffi::allocfunc> {
|
||||
use pyo3::impl_::pyclass::*;
|
||||
let collector = PyClassImplCollector::<Self>::new();
|
||||
collector.alloc_impl()
|
||||
}
|
||||
fn get_free() -> Option<pyo3::ffi::freefunc> {
|
||||
use pyo3::impl_::pyclass::*;
|
||||
let collector = PyClassImplCollector::<Self>::new();
|
||||
collector.free_impl()
|
||||
}
|
||||
}
|
||||
|
||||
impl ::pyo3::impl_::pyclass::PyClassDescriptors<MyClass>
|
||||
|
|
|
@ -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}}
|
||||
```
|
||||
|
||||
|
|
|
@ -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 <module>
|
||||
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<i32, PyErr> {
|
||||
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<i32, PyErr> {
|
||||
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<Self> {
|
||||
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<Self> {
|
||||
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<Self> {
|
||||
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<Self> {
|
||||
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<i32, PyErr> {
|
||||
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<bool> {
|
||||
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<Self> {
|
||||
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<Self> {
|
||||
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<Self> {
|
||||
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<Self> {
|
||||
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::<Number>()?;
|
||||
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<T>`.
|
||||
- `&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<i32, PyErr> {
|
||||
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
|
|
@ -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::<Number>()?;
|
||||
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
|
||||
<builtins.Number object at 0x000002B4D185D7D0>
|
||||
```
|
||||
|
||||
### 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<Py<PyAny>> = 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<bool> {
|
||||
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<bool> {
|
||||
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::<Number>()?;
|
||||
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
|
|
@ -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 `<self>`. It can be
|
||||
`&self`, `&mut self` or a `PyCell` reference like `self_: PyRef<Self>` and
|
||||
`self_: PyRefMut<Self>`, 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__(<self>) -> object (str)`
|
||||
- `__repr__(<self>) -> object (str)`
|
||||
|
||||
- `__hash__(<self>) -> isize`
|
||||
|
||||
Objects that compare equal must have the same hash value.
|
||||
<details>
|
||||
<summary>Disabling Python's default hash</summary>
|
||||
|
||||
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:
|
|||
</details>
|
||||
|
||||
- `__richcmp__(<self>, object, pyo3::basic::CompareOp) -> object`
|
||||
|
||||
Overloads Python comparison operations (`==`, `!=`, `<`, `<=`, `>`, and `>=`).
|
||||
The `CompareOp` argument indicates the comparison operation being performed.
|
||||
<details>
|
||||
<summary>Return type</summary>
|
||||
The return type will normally be `PyResult<bool>`, 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`.
|
||||
</details>
|
||||
|
||||
- `__getattr__(<self>, object) -> object`
|
||||
- `__setattr__(<self>, object, object) -> ()`
|
||||
- `__getattribute__(<self>, object) -> object`
|
||||
<details>
|
||||
<summary>Differences between `__getattr__` and `__getattribute__`</summary>
|
||||
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__()`.
|
||||
</details>
|
||||
|
||||
- `__setattr__(<self>, value: object) -> ()`
|
||||
- `__delattr__(<self>, object) -> ()`
|
||||
|
||||
Overrides attribute access.
|
||||
|
||||
- `__bool__(<self>) -> bool`
|
||||
|
||||
Determines the "truthyness" of an object.
|
||||
|
||||
- `__call__(<self>, ...) -> object` - here, any argument list can be defined
|
||||
as for normal `pymethods`
|
||||
|
||||
#### Iterable objects
|
||||
### Iterable objects
|
||||
|
||||
Iterators can be defined using these methods:
|
||||
|
||||
- `__iter__(<self>) -> object`
|
||||
- `__next__(<self>) -> Option<object> or IterNextOutput` ([see details](#returning-a-value-from-iteration))
|
||||
|
||||
#### Awaitable objects
|
||||
Returning `None` from `__next__` indicates that that there are no further items.
|
||||
|
||||
Example:
|
||||
|
||||
```rust
|
||||
use pyo3::prelude::*;
|
||||
|
||||
#[pyclass]
|
||||
struct MyIterator {
|
||||
iter: Box<dyn Iterator<Item = PyObject> + Send>,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl 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, the iterable
|
||||
only needs to implement `__iter__()` while the iterator must implement both
|
||||
`__iter__()` and `__next__()`. For example:
|
||||
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
|
||||
#[pyclass]
|
||||
struct Iter {
|
||||
inner: std::vec::IntoIter<usize>,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl 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>,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl Container {
|
||||
fn __iter__(slf: PyRef<'_, Self>) -> PyResult<Py<Iter>> {
|
||||
let iter = Iter {
|
||||
inner: slf.iter.clone().into_iter(),
|
||||
};
|
||||
Py::new(slf.py(), iter)
|
||||
}
|
||||
}
|
||||
|
||||
# Python::with_gil(|py| {
|
||||
# let container = Container { iter: vec![1, 2, 3, 4] };
|
||||
# let inst = pyo3::PyCell::new(py, container).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/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`] enum to both `Yield` values
|
||||
and `Return` a final value - see its docs for further details and an example.
|
||||
|
||||
### Awaitable objects
|
||||
|
||||
- `__await__(<self>) -> object`
|
||||
- `__aiter__(<self>) -> object`
|
||||
- `__anext__(<self>) -> Option<object> or IterANextOutput`
|
||||
|
||||
#### Mapping & Sequence types
|
||||
### Mapping & Sequence types
|
||||
|
||||
- `__len__(<self>) -> usize`
|
||||
|
||||
Implements the built-in function `len()` for the sequence.
|
||||
|
||||
- `__contains__(<self>, object) -> bool`
|
||||
|
||||
Implements membership test operators.
|
||||
Should return true if `item` is in `self`, false otherwise.
|
||||
For objects that don’t define `__contains__()`, the membership test simply
|
||||
traverses the sequence until it finds a match.
|
||||
|
||||
<details>
|
||||
<summary>Disabling Python's default contains</summary>
|
||||
|
||||
By default, all `#[pyclass]` types with an `__iter__` method support a default implementation of the `in` operator. Types which do not want this can override this by setting `__contains__` to `None`. This is the same mechanism as for a pure-Python class. This is done like so:
|
||||
By default, all `#[pyclass]` types with an `__iter__` method support a
|
||||
default implementation of the `in` operator. Types which do not want this
|
||||
can override this by setting `__contains__` to `None`. This is the same
|
||||
mechanism as for a pure-Python class. This is done like so:
|
||||
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
|
@ -107,38 +224,62 @@ given signatures should be interpreted as follows:
|
|||
}
|
||||
```
|
||||
</details>
|
||||
|
||||
- `__getitem__(<self>, object) -> object`
|
||||
|
||||
Implements retrieval of the `self[a]` element.
|
||||
|
||||
*Note:* Negative integer indexes are not handled specially.
|
||||
|
||||
- `__setitem__(<self>, object, object) -> ()`
|
||||
|
||||
Implements assignment to the `self[a]` element.
|
||||
Should only be implemented if elements can be replaced.
|
||||
|
||||
- `__delitem__(<self>, object) -> ()`
|
||||
|
||||
#### Descriptors
|
||||
Implements deletion of the `self[a]` element.
|
||||
Should only be implemented if elements can be deleted.
|
||||
|
||||
* `fn __concat__(&self, other: impl FromPyObject) -> PyResult<impl ToPyObject>`
|
||||
|
||||
Concatenates two sequences.
|
||||
Used by the `+` operator, after trying the numeric addition via
|
||||
the `__add__` and `__radd__` methods.
|
||||
|
||||
* `fn __repeat__(&self, count: isize) -> PyResult<impl ToPyObject>`
|
||||
|
||||
Repeats the sequence `count` times.
|
||||
Used by the `*` operator, after trying the numeric multiplication via
|
||||
the `__mul__` and `__rmul__` methods.
|
||||
|
||||
* `fn __inplace_concat__(&self, other: impl FromPyObject) -> PyResult<impl ToPyObject>`
|
||||
|
||||
Concatenates two sequences.
|
||||
Used by the `+=` operator, after trying the numeric addition via
|
||||
the `__iadd__` method.
|
||||
|
||||
* `fn __inplace_repeat__(&self, count: isize) -> PyResult<impl ToPyObject>`
|
||||
|
||||
Concatenates two sequences.
|
||||
Used by the `*=` operator, after trying the numeric multiplication via
|
||||
the `__imul__` method.
|
||||
|
||||
|
||||
### Descriptors
|
||||
|
||||
- `__get__(<self>, object, object) -> object`
|
||||
- `__set__(<self>, object, object) -> ()`
|
||||
- `__delete__(<self>, object) -> ()`
|
||||
|
||||
#### Numeric types
|
||||
### Numeric types
|
||||
|
||||
Binary arithmetic operations (`+`, `-`, `*`, `@`, `/`, `//`, `%`, `divmod()`,
|
||||
`pow()` and `**`, `<<`, `>>`, `&`, `^`, and `|`) and their reflected versions:
|
||||
|
||||
(If the `object` is not of the type specified in the signature, the generated code
|
||||
will automatically `return NotImplemented`.)
|
||||
|
||||
- `__pos__(<self>) -> object`
|
||||
- `__neg__(<self>) -> object`
|
||||
- `__abs__(<self>) -> object`
|
||||
- `__invert__(<self>) -> object`
|
||||
- `__index__(<self>) -> object (int)`
|
||||
- `__int__(<self>) -> object (int)`
|
||||
- `__float__(<self>) -> object (float)`
|
||||
- `__iadd__(<self>, object) -> ()`
|
||||
- `__isub__(<self>, object) -> ()`
|
||||
- `__imul__(<self>, object) -> ()`
|
||||
- `__imatmul__(<self>, object) -> ()`
|
||||
- `__itruediv__(<self>, object) -> ()`
|
||||
- `__ifloordiv__(<self>, object) -> ()`
|
||||
- `__imod__(<self>, object) -> ()`
|
||||
- `__ipow__(<self>, object, object) -> ()`
|
||||
- `__ilshift__(<self>, object) -> ()`
|
||||
- `__irshift__(<self>, object) -> ()`
|
||||
- `__iand__(<self>, object) -> ()`
|
||||
- `__ixor__(<self>, object) -> ()`
|
||||
- `__ior__(<self>, object) -> ()`
|
||||
- `__add__(<self>, object) -> object`
|
||||
- `__radd__(<self>, object) -> object`
|
||||
- `__sub__(<self>, object) -> object`
|
||||
|
@ -168,20 +309,91 @@ given signatures should be interpreted as follows:
|
|||
- `__pow__(<self>, object, object) -> object`
|
||||
- `__rpow__(<self>, object, object) -> object`
|
||||
|
||||
#### Buffer objects
|
||||
In-place assignment operations (`+=`, `-=`, `*=`, `@=`, `/=`, `//=`, `%=`,
|
||||
`**=`, `<<=`, `>>=`, `&=`, `^=`, `|=`):
|
||||
|
||||
- `__iadd__(<self>, object) -> ()`
|
||||
- `__isub__(<self>, object) -> ()`
|
||||
- `__imul__(<self>, object) -> ()`
|
||||
- `__imatmul__(<self>, object) -> ()`
|
||||
- `__itruediv__(<self>, object) -> ()`
|
||||
- `__ifloordiv__(<self>, object) -> ()`
|
||||
- `__imod__(<self>, object) -> ()`
|
||||
- `__ipow__(<self>, object, object) -> ()`
|
||||
- `__ilshift__(<self>, object) -> ()`
|
||||
- `__irshift__(<self>, object) -> ()`
|
||||
- `__iand__(<self>, object) -> ()`
|
||||
- `__ixor__(<self>, object) -> ()`
|
||||
- `__ior__(<self>, object) -> ()`
|
||||
|
||||
Unary operations (`-`, `+`, `abs()` and `~`):
|
||||
|
||||
- `__pos__(<self>) -> object`
|
||||
- `__neg__(<self>) -> object`
|
||||
- `__abs__(<self>) -> object`
|
||||
- `__invert__(<self>) -> object`
|
||||
|
||||
Coercions:
|
||||
|
||||
- `__index__(<self>) -> object (int)`
|
||||
- `__int__(<self>) -> object (int)`
|
||||
- `__float__(<self>) -> object (float)`
|
||||
|
||||
### Buffer objects
|
||||
|
||||
- `__getbuffer__(<self>, *mut ffi::Py_buffer, flags) -> ()`
|
||||
- `__releasebuffer__(<self>, *mut ffi::Py_buffer)` (no return value, not even `PyResult`)
|
||||
|
||||
#### Garbage Collector Integration
|
||||
### 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 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.
|
||||
|
||||
- `__traverse__(<self>, pyo3::class::gc::PyVisit<'_>) -> Result<(), pyo3::class::gc::PyTraverseError>`
|
||||
- `__clear__(<self>) -> ()`
|
||||
|
||||
Example:
|
||||
|
||||
```rust
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::PyTraverseError;
|
||||
use pyo3::gc::PyVisit;
|
||||
|
||||
#[pyclass]
|
||||
struct ClassWithGCSupport {
|
||||
obj: Option<PyObject>,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl ClassWithGCSupport {
|
||||
fn __traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError> {
|
||||
if let Some(obj) = &self.obj {
|
||||
visit.call(obj)?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn __clear__(&mut self) {
|
||||
// Clear reference, this decrements ref counter.
|
||||
self.obj = None;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
[`IterNextOutput`]: {{#PYO3_DOCS_URL}}/pyo3/class/iter/enum.IterNextOutput.html
|
||||
|
||||
TODO; see [#1884](https://github.com/PyO3/pyo3/issues/1884)
|
||||
|
||||
### `#[pyproto]` traits
|
||||
|
||||
PyO3 can use the `#[pyproto]` attribute in combination with special traits to implement the magic methods which need slots. The special traits are listed below. See also the [documentation for the `pyo3::class` module]({{#PYO3_DOCS_URL}}/pyo3/class/index.html).
|
||||
|
||||
Before PyO3 0.15 this was the only supported solution for implementing magic methods. Due to complexity in the implementation and usage, these traits are expected to be deprecated in PyO3 0.16 in favour of the `#[pymethods]` solution.
|
||||
Due to complexity in the implementation and usage, these traits are deprecated in PyO3 0.16 in favour of the `#[pymethods]` solution.
|
||||
|
||||
All `#[pyproto]` methods 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.
|
||||
|
||||
|
@ -189,44 +401,15 @@ All `#[pyproto]` methods can return `T` instead of `PyResult<T>` if the method i
|
|||
|
||||
The [`PyObjectProtocol`] trait provides several basic customizations.
|
||||
|
||||
##### Attribute access
|
||||
|
||||
To customize object attribute access, define the following methods:
|
||||
|
||||
* `fn __str__(&self) -> PyResult<impl ToPyObject<ObjectType=PyString>>`
|
||||
* `fn __repr__(&self) -> PyResult<impl ToPyObject<ObjectType=PyString>>`
|
||||
* `fn __hash__(&self) -> PyResult<impl PrimInt>`
|
||||
* `fn __richcmp__(&self, other: impl FromPyObject, op: CompareOp) -> PyResult<impl ToPyObject>`
|
||||
* `fn __getattr__(&self, name: impl FromPyObject) -> PyResult<impl IntoPy<PyObject>>`
|
||||
* `fn __setattr__(&mut self, name: impl FromPyObject, value: impl FromPyObject) -> PyResult<()>`
|
||||
* `fn __delattr__(&mut self, name: impl 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>`.
|
||||
|
||||
##### 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 can be implemented to emulate [numeric types](https://docs.python.org/3/reference/datamodel.html#emulating-numeric-types).
|
||||
|
@ -246,13 +429,7 @@ The [`PyNumberProtocol`] trait can be implemented to emulate [numeric types](htt
|
|||
* `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.
|
||||
|
||||
These methods are called to implement the binary arithmetic operations.
|
||||
|
||||
The reflected operations are also available:
|
||||
|
||||
|
@ -274,9 +451,6 @@ The reflected operations are also available:
|
|||
The code generated for these methods expect that all arguments match the
|
||||
signature, or raise a TypeError.
|
||||
|
||||
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<()>`
|
||||
|
@ -293,7 +467,7 @@ This trait also has support the augmented arithmetic assignments (`+=`, `-=`,
|
|||
* `fn __ixor__(&'p mut self, other: impl FromPyObject) -> PyResult<()>`
|
||||
|
||||
|
||||
The following methods implement the unary arithmetic operations (`-`, `+`, `abs()` and `~`):
|
||||
The following methods implement the unary arithmetic operations:
|
||||
|
||||
* `fn __neg__(&'p self) -> PyResult<impl ToPyObject>`
|
||||
* `fn __pos__(&'p self) -> PyResult<impl ToPyObject>`
|
||||
|
@ -304,9 +478,6 @@ Support for coercions:
|
|||
|
||||
* `fn __int__(&'p self) -> PyResult<impl ToPyObject>`
|
||||
* `fn __float__(&'p self) -> PyResult<impl ToPyObject>`
|
||||
|
||||
Other:
|
||||
|
||||
* `fn __index__(&'p self) -> PyResult<impl ToPyObject>`
|
||||
|
||||
#### Emulating sequential containers (such as lists or tuples)
|
||||
|
@ -405,152 +576,26 @@ For a mapping, the keys may be Python objects of arbitrary type.
|
|||
The same exceptions should be raised for improper key values as
|
||||
for the `__getitem__()` method.
|
||||
|
||||
#### 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
|
||||
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(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`]({{#PYO3_DOCS_URL}}/pyo3/class/iter/trait.PyIterProtocol.html) trait.
|
||||
Iterators can be defined using the [`PyIterProtocol`] 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>>>`
|
||||
* `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
|
||||
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:
|
||||
For details, look at the `#[pymethods]` regarding iterator methods.
|
||||
|
||||
```rust
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::PyIterProtocol;
|
||||
#### Garbage Collector Integration
|
||||
|
||||
#[pyclass]
|
||||
struct MyIterator {
|
||||
iter: Box<dyn 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)
|
||||
}
|
||||
}
|
||||
|
||||
# Python::with_gil(|py| {
|
||||
# let container = Container { iter: vec![1, 2, 3, 4] };
|
||||
# let inst = pyo3::PyCell::new(py, container).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`]({{#PYO3_DOCS_URL}}/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.
|
||||
Implement the [`PyGCProtocol`] trait for your struct.
|
||||
For details, look at the `#[pymethods]` regarding GC methods.
|
||||
|
||||
[`PyGCProtocol`]: {{#PYO3_DOCS_URL}}/pyo3/class/gc/trait.PyGCProtocol.html
|
||||
[`PyMappingProtocol`]: {{#PYO3_DOCS_URL}}/pyo3/class/mapping/trait.PyMappingProtocol.html
|
||||
[`PyNumberProtocol`]: {{#PYO3_DOCS_URL}}/pyo3/class/number/trait.PyNumberProtocol.html
|
||||
[`PyObjectProtocol`]: {{#PYO3_DOCS_URL}}/pyo3/class/basic/trait.PyObjectProtocol.html
|
||||
[`PySequenceProtocol`]: {{#PYO3_DOCS_URL}}/pyo3/class/sequence/trait.PySequenceProtocol.html
|
||||
[`PyIterProtocol`]: {{#PYO3_DOCS_URL}}/pyo3/class/iter/trait.PyIterProtocol.html
|
||||
|
|
|
@ -35,7 +35,7 @@ structs is not supported.
|
|||
The derivation generates code that will attempt to access the attribute `my_string` on
|
||||
the Python object, i.e. `obj.getattr("my_string")`, and call `extract()` on the attribute.
|
||||
|
||||
```
|
||||
```rust
|
||||
use pyo3::prelude::*;
|
||||
|
||||
#[derive(FromPyObject)]
|
||||
|
@ -65,7 +65,7 @@ struct RustyStruct {
|
|||
|
||||
By setting the `#[pyo3(item)]` attribute on the field, PyO3 will attempt to extract the value by calling the `get_item` method on the Python object.
|
||||
|
||||
```
|
||||
```rust
|
||||
use pyo3::prelude::*;
|
||||
|
||||
|
||||
|
@ -90,7 +90,7 @@ struct RustyStruct {
|
|||
|
||||
The argument passed to `getattr` and `get_item` can also be configured:
|
||||
|
||||
```
|
||||
```rust
|
||||
use pyo3::prelude::*;
|
||||
|
||||
#[derive(FromPyObject)]
|
||||
|
@ -135,7 +135,7 @@ Tuple structs are also supported but do not allow customizing the extraction. Th
|
|||
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.
|
||||
|
||||
```
|
||||
```rust
|
||||
use pyo3::prelude::*;
|
||||
|
||||
#[derive(FromPyObject)]
|
||||
|
@ -158,7 +158,7 @@ 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
|
||||
```
|
||||
```rust
|
||||
use pyo3::prelude::*;
|
||||
|
||||
#[derive(FromPyObject)]
|
||||
|
@ -184,7 +184,7 @@ in extracting directly from the input object, i.e. `obj.extract()`, rather than
|
|||
an item or attribute. This behaviour is enabled per default for newtype structs and tuple-variants
|
||||
with a single field.
|
||||
|
||||
```
|
||||
```rust
|
||||
use pyo3::prelude::*;
|
||||
|
||||
#[derive(FromPyObject)]
|
||||
|
@ -223,7 +223,7 @@ i.e. a tuple variant assumes that the input is a Python tuple, and a struct vari
|
|||
extracting fields as attributes but can be configured in the same manner. The `transparent`
|
||||
attribute can be applied to single-field-variants.
|
||||
|
||||
```
|
||||
```rust
|
||||
use pyo3::prelude::*;
|
||||
|
||||
#[derive(FromPyObject)]
|
||||
|
@ -253,7 +253,7 @@ enum RustyEnum<'a> {
|
|||
# Python::with_gil(|py| -> PyResult<()> {
|
||||
# {
|
||||
# let thing = 42_u8.to_object(py);
|
||||
# let rust_thing: RustyEnum = thing.extract(py)?;
|
||||
# let rust_thing: RustyEnum<'_> = thing.extract(py)?;
|
||||
#
|
||||
# assert_eq!(
|
||||
# 42,
|
||||
|
@ -265,7 +265,7 @@ enum RustyEnum<'a> {
|
|||
# }
|
||||
# {
|
||||
# let thing = PyString::new(py, "text");
|
||||
# let rust_thing: RustyEnum = thing.extract()?;
|
||||
# let rust_thing: RustyEnum<'_> = thing.extract()?;
|
||||
#
|
||||
# assert_eq!(
|
||||
# "text",
|
||||
|
@ -277,7 +277,7 @@ enum RustyEnum<'a> {
|
|||
# }
|
||||
# {
|
||||
# let thing = (32_u8, 73_u8).to_object(py);
|
||||
# let rust_thing: RustyEnum = thing.extract(py)?;
|
||||
# let rust_thing: RustyEnum<'_> = thing.extract(py)?;
|
||||
#
|
||||
# assert_eq!(
|
||||
# (32, 73),
|
||||
|
@ -289,7 +289,7 @@ enum RustyEnum<'a> {
|
|||
# }
|
||||
# {
|
||||
# let thing = ("foo", 73_u8).to_object(py);
|
||||
# let rust_thing: RustyEnum = thing.extract(py)?;
|
||||
# let rust_thing: RustyEnum<'_> = thing.extract(py)?;
|
||||
#
|
||||
# assert_eq!(
|
||||
# (String::from("foo"), 73),
|
||||
|
@ -313,7 +313,7 @@ enum RustyEnum<'a> {
|
|||
#
|
||||
# let class = module.getattr("Foo")?;
|
||||
# let instance = class.call0()?;
|
||||
# let rust_thing: RustyEnum = instance.extract()?;
|
||||
# let rust_thing: RustyEnum<'_> = instance.extract()?;
|
||||
#
|
||||
# assert_eq!(
|
||||
# (0, 1, 2),
|
||||
|
@ -337,7 +337,7 @@ enum RustyEnum<'a> {
|
|||
#
|
||||
# let class = module.getattr("Foo")?;
|
||||
# let instance = class.call0()?;
|
||||
# let rust_thing: RustyEnum = instance.extract()?;
|
||||
# let rust_thing: RustyEnum<'_> = instance.extract()?;
|
||||
#
|
||||
# assert_eq!(
|
||||
# (3, 4),
|
||||
|
@ -350,7 +350,7 @@ enum RustyEnum<'a> {
|
|||
#
|
||||
# {
|
||||
# let thing = PyBytes::new(py, b"text");
|
||||
# let rust_thing: RustyEnum = thing.extract()?;
|
||||
# let rust_thing: RustyEnum<'_> = thing.extract()?;
|
||||
#
|
||||
# assert_eq!(
|
||||
# b"text",
|
||||
|
@ -370,7 +370,7 @@ tested variants is returned. The names reported in the error message can be cust
|
|||
through the `#[pyo3(annotation = "name")]` attribute, e.g. to use conventional Python type
|
||||
names:
|
||||
|
||||
```
|
||||
```rust
|
||||
use pyo3::prelude::*;
|
||||
|
||||
#[derive(FromPyObject)]
|
||||
|
@ -442,6 +442,10 @@ If the input is neither a string nor an integer, the error message will be:
|
|||
- `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`
|
||||
- `pyo3(from_py_with = "...")`
|
||||
- apply a custom function to convert the field from Python the desired Rust type.
|
||||
- the argument must be the name of the function as a string.
|
||||
- the function signature must be `fn(&PyAny) -> PyResult<T>` where `T` is the Rust type of the argument.
|
||||
|
||||
### `IntoPy<T>`
|
||||
|
||||
|
@ -454,13 +458,13 @@ All types in PyO3 implement this trait, as does a `#[pyclass]` which doesn't use
|
|||
Occasionally you may choose to implement this for custom types which are mapped to Python types
|
||||
_without_ having a unique python type.
|
||||
|
||||
```
|
||||
```rust
|
||||
use pyo3::prelude::*;
|
||||
|
||||
struct MyPyObjectWrapper(PyObject);
|
||||
|
||||
impl IntoPy<PyObject> for MyPyObjectWrapper {
|
||||
fn into_py(self, py: Python) -> PyObject {
|
||||
fn into_py(self, py: Python<'_>) -> PyObject {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
|
|
@ -118,7 +118,7 @@ Export an async function that makes use of `async-std`:
|
|||
use pyo3::{prelude::*, wrap_pyfunction};
|
||||
|
||||
#[pyfunction]
|
||||
fn rust_sleep(py: Python) -> PyResult<&PyAny> {
|
||||
fn rust_sleep(py: Python<'_>) -> PyResult<&PyAny> {
|
||||
pyo3_asyncio::async_std::future_into_py(py, async {
|
||||
async_std::task::sleep(std::time::Duration::from_secs(1)).await;
|
||||
Ok(Python::with_gil(|py| py.None()))
|
||||
|
@ -126,7 +126,7 @@ fn rust_sleep(py: Python) -> PyResult<&PyAny> {
|
|||
}
|
||||
|
||||
#[pymodule]
|
||||
fn my_async_module(py: Python, m: &PyModule) -> PyResult<()> {
|
||||
fn my_async_module(py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||
m.add_function(wrap_pyfunction!(rust_sleep, m)?)?;
|
||||
|
||||
Ok(())
|
||||
|
@ -142,7 +142,7 @@ If you want to use `tokio` instead, here's what your module should look like:
|
|||
use pyo3::{prelude::*, wrap_pyfunction};
|
||||
|
||||
#[pyfunction]
|
||||
fn rust_sleep(py: Python) -> PyResult<&PyAny> {
|
||||
fn rust_sleep(py: Python<'_>) -> PyResult<&PyAny> {
|
||||
pyo3_asyncio::tokio::future_into_py(py, async {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
Ok(Python::with_gil(|py| py.None()))
|
||||
|
@ -150,7 +150,7 @@ fn rust_sleep(py: Python) -> PyResult<&PyAny> {
|
|||
}
|
||||
|
||||
#[pymodule]
|
||||
fn my_async_module(py: Python, m: &PyModule) -> PyResult<()> {
|
||||
fn my_async_module(py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||
m.add_function(wrap_pyfunction!(rust_sleep, m)?)?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -316,7 +316,7 @@ async fn rust_sleep() {
|
|||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn call_rust_sleep(py: Python) -> PyResult<&PyAny> {
|
||||
fn call_rust_sleep(py: Python<'_>) -> PyResult<&PyAny> {
|
||||
pyo3_asyncio::async_std::future_into_py(py, async move {
|
||||
rust_sleep().await;
|
||||
Ok(Python::with_gil(|py| py.None()))
|
||||
|
@ -466,7 +466,7 @@ tokio = "1.4"
|
|||
use pyo3::{prelude::*, wrap_pyfunction};
|
||||
|
||||
#[pyfunction]
|
||||
fn rust_sleep(py: Python) -> PyResult<&PyAny> {
|
||||
fn rust_sleep(py: Python<'_>) -> PyResult<&PyAny> {
|
||||
pyo3_asyncio::tokio::future_into_py(py, async {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
Ok(Python::with_gil(|py| py.None()))
|
||||
|
@ -474,7 +474,7 @@ fn rust_sleep(py: Python) -> PyResult<&PyAny> {
|
|||
}
|
||||
|
||||
#[pymodule]
|
||||
fn my_async_module(_py: Python, m: &PyModule) -> PyResult<()> {
|
||||
fn my_async_module(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||
m.add_function(wrap_pyfunction!(rust_sleep, m)?)?;
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -38,7 +38,7 @@ the module like this, so that it is importable from Python:
|
|||
create_exception!(mymodule, CustomError, PyException);
|
||||
|
||||
#[pymodule]
|
||||
fn mymodule(py: Python, m: &PyModule) -> PyResult<()> {
|
||||
fn mymodule(py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||
// ... other elements added to module ...
|
||||
m.add("CustomError", py.get_type::<CustomError>())?;
|
||||
|
||||
|
@ -151,7 +151,7 @@ struct CustomIOError;
|
|||
impl std::error::Error for CustomIOError {}
|
||||
|
||||
impl fmt::Display for CustomIOError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Oh no!")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -116,7 +116,7 @@ struct Outer {
|
|||
#[pymethods]
|
||||
impl Outer {
|
||||
#[new]
|
||||
fn __new__(py: Python) -> PyResult<Self> {
|
||||
fn __new__(py: Python<'_>) -> PyResult<Self> {
|
||||
Ok(Self {
|
||||
inner: Py::new(py, Inner {})?,
|
||||
})
|
||||
|
|
|
@ -73,9 +73,7 @@ This feature enables the `#[pyproto]` macro, which is an alternative (older, soo
|
|||
|
||||
### `nightly`
|
||||
|
||||
The `nightly` feature needs the nightly Rust compiler. This allows PyO3 to use Rust's unstable specialization feature to apply the following optimizations:
|
||||
- `FromPyObject` for `Vec` and `[T;N]` can perform a `memcpy` when the object supports the Python buffer protocol.
|
||||
- `ToBorrowedObject` can skip a reference count increase when the provided object is a Python native type.
|
||||
The `nightly` feature needs the nightly Rust compiler. This allows PyO3 to use the auto_traits and negative_impls features to fix the `Python::allow_threads` function.
|
||||
|
||||
### `resolve-config`
|
||||
|
||||
|
@ -117,6 +115,11 @@ Enables (de)serialization of Py<T> objects via [serde](https://serde.rs/).
|
|||
This allows to use [`#[derive(Serialize, Deserialize)`](https://serde.rs/derive.html) on structs that hold references to `#[pyclass]` instances
|
||||
|
||||
```rust
|
||||
# #[cfg(feature = "serde")]
|
||||
# #[allow(dead_code)]
|
||||
# mod serde_only {
|
||||
# use pyo3::prelude::*;
|
||||
# use serde::{Deserialize, Serialize};
|
||||
|
||||
#[pyclass]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
|
@ -130,4 +133,5 @@ struct User {
|
|||
username: String,
|
||||
permissions: Vec<Py<Permission>>
|
||||
}
|
||||
# }
|
||||
```
|
||||
|
|
|
@ -13,7 +13,7 @@ fn double(x: usize) -> usize {
|
|||
}
|
||||
|
||||
#[pymodule]
|
||||
fn my_extension(py: Python, m: &PyModule) -> PyResult<()> {
|
||||
fn my_extension(py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||
m.add_function(wrap_pyfunction!(double, m)?)?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ The `#[pyo3]` attribute can be used to modify properties of the generated Python
|
|||
fn no_args_py() -> usize { 42 }
|
||||
|
||||
#[pymodule]
|
||||
fn module_with_functions(py: Python, m: &PyModule) -> PyResult<()> {
|
||||
fn module_with_functions(py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||
m.add_function(wrap_pyfunction!(no_args_py, m)?)?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -113,7 +113,7 @@ The `#[pyo3]` attribute can be used to modify properties of the generated Python
|
|||
}
|
||||
|
||||
#[pymodule]
|
||||
fn module_with_fn(py: Python, m: &PyModule) -> PyResult<()> {
|
||||
fn module_with_fn(py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||
m.add_function(wrap_pyfunction!(pyfunction_with_module, m)?)
|
||||
}
|
||||
```
|
||||
|
@ -132,7 +132,7 @@ fn num_kwds(kwds: Option<&PyDict>) -> usize {
|
|||
}
|
||||
|
||||
#[pymodule]
|
||||
fn module_with_functions(py: Python, m: &PyModule) -> PyResult<()> {
|
||||
fn module_with_functions(py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||
m.add_function(wrap_pyfunction!(num_kwds, m)?).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
@ -273,7 +273,7 @@ An example of `#[pyfn]` is below:
|
|||
use pyo3::prelude::*;
|
||||
|
||||
#[pymodule]
|
||||
fn my_extension(py: Python, m: &PyModule) -> PyResult<()> {
|
||||
fn my_extension(py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||
|
||||
#[pyfn(m)]
|
||||
fn double(x: usize) -> usize {
|
||||
|
@ -291,7 +291,7 @@ documented in the rest of this chapter. The code above is expanded to the follow
|
|||
use pyo3::prelude::*;
|
||||
|
||||
#[pymodule]
|
||||
fn my_extension(py: Python, m: &PyModule) -> PyResult<()> {
|
||||
fn my_extension(py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||
|
||||
#[pyfunction]
|
||||
fn double(x: usize) -> usize {
|
||||
|
|
|
@ -23,11 +23,16 @@ held. (If PyO3 could not assume this, every PyO3 API would need to take a
|
|||
very simple and easy-to-understand programs like this:
|
||||
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
# use pyo3::types::PyString;
|
||||
# fn main() -> PyResult<()> {
|
||||
Python::with_gil(|py| -> PyResult<()> {
|
||||
let hello: &PyString = py.eval("\"Hello World!\"", None, None)?.extract()?;
|
||||
println!("Python says: {}", hello);
|
||||
Ok(())
|
||||
})?;
|
||||
# Ok(())
|
||||
# }
|
||||
```
|
||||
|
||||
Internally, calling `Python::with_gil()` or `Python::acquire_gil()` creates a
|
||||
|
@ -39,6 +44,9 @@ it owns are decreased, releasing them to the Python garbage collector. Most
|
|||
of the time we don't have to think about this, but consider the following:
|
||||
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
# use pyo3::types::PyString;
|
||||
# fn main() -> PyResult<()> {
|
||||
Python::with_gil(|py| -> PyResult<()> {
|
||||
for _ in 0..10 {
|
||||
let hello: &PyString = py.eval("\"Hello World!\"", None, None)?.extract()?;
|
||||
|
@ -47,6 +55,8 @@ Python::with_gil(|py| -> PyResult<()> {
|
|||
// There are 10 copies of `hello` on Python's heap here.
|
||||
Ok(())
|
||||
})?;
|
||||
# Ok(())
|
||||
# }
|
||||
```
|
||||
|
||||
We might assume that the `hello` variable's memory is freed at the end of each
|
||||
|
@ -62,6 +72,9 @@ In general we don't want unbounded memory growth during loops! One workaround
|
|||
is to acquire and release the GIL with each iteration of the loop.
|
||||
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
# use pyo3::types::PyString;
|
||||
# fn main() -> PyResult<()> {
|
||||
for _ in 0..10 {
|
||||
Python::with_gil(|py| -> PyResult<()> {
|
||||
let hello: &PyString = py.eval("\"Hello World!\"", None, None)?.extract()?;
|
||||
|
@ -69,6 +82,8 @@ for _ in 0..10 {
|
|||
Ok(())
|
||||
})?; // only one copy of `hello` at a time
|
||||
}
|
||||
# Ok(())
|
||||
# }
|
||||
```
|
||||
|
||||
It might not be practical or performant to acquire and release the GIL so many
|
||||
|
@ -76,6 +91,9 @@ times. Another workaround is to work with the `GILPool` object directly, but
|
|||
this is unsafe.
|
||||
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
# use pyo3::types::PyString;
|
||||
# fn main() -> PyResult<()> {
|
||||
Python::with_gil(|py| -> PyResult<()> {
|
||||
for _ in 0..10 {
|
||||
let pool = unsafe { py.new_pool() };
|
||||
|
@ -85,6 +103,8 @@ Python::with_gil(|py| -> PyResult<()> {
|
|||
}
|
||||
Ok(())
|
||||
})?;
|
||||
# Ok(())
|
||||
# }
|
||||
```
|
||||
|
||||
The unsafe method `Python::new_pool` allows you to create a nested `GILPool`
|
||||
|
@ -112,11 +132,16 @@ What happens to the memory when the last `Py<PyAny>` is dropped and its
|
|||
reference count reaches zero? It depends whether or not we are holding the GIL.
|
||||
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
# use pyo3::types::PyString;
|
||||
# fn main() -> PyResult<()> {
|
||||
Python::with_gil(|py| -> PyResult<()> {
|
||||
let hello: Py<PyString> = py.eval("\"Hello World!\"", None, None)?.extract())?;
|
||||
let hello: Py<PyString> = py.eval("\"Hello World!\"", None, None)?.extract()?;
|
||||
println!("Python says: {}", hello.as_ref(py));
|
||||
Ok(())
|
||||
});
|
||||
})?;
|
||||
# Ok(())
|
||||
# }
|
||||
```
|
||||
|
||||
At the end of the `Python::with_gil()` closure `hello` is dropped, and then the
|
||||
|
@ -129,8 +154,11 @@ This example wasn't very interesting. We could have just used a GIL-bound
|
|||
we are *not* holding the GIL?
|
||||
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
# use pyo3::types::PyString;
|
||||
# fn main() -> PyResult<()> {
|
||||
let hello: Py<PyString> = Python::with_gil(|py| {
|
||||
Py<PyString> = py.eval("\"Hello World!\"", None, None)?.extract())
|
||||
py.eval("\"Hello World!\"", None, None)?.extract()
|
||||
})?;
|
||||
// Do some stuff...
|
||||
// Now sometime later in the program we want to access `hello`.
|
||||
|
@ -142,7 +170,10 @@ drop(hello); // Memory *not* released here.
|
|||
// Sometime later we need the GIL again for something...
|
||||
Python::with_gil(|py|
|
||||
// Memory for `hello` is released here.
|
||||
# ()
|
||||
);
|
||||
# Ok(())
|
||||
# }
|
||||
```
|
||||
|
||||
When `hello` is dropped *nothing* happens to the pointed-to memory on Python's
|
||||
|
@ -154,8 +185,11 @@ We can avoid the delay in releasing memory if we are careful to drop the
|
|||
`Py<Any>` while the GIL is held.
|
||||
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
# use pyo3::types::PyString;
|
||||
# fn main() -> PyResult<()> {
|
||||
let hello: Py<PyString> = Python::with_gil(|py| {
|
||||
Py<PyString> = py.eval("\"Hello World!\"", None, None)?.extract())
|
||||
py.eval("\"Hello World!\"", None, None)?.extract()
|
||||
})?;
|
||||
// Do some stuff...
|
||||
// Now sometime later in the program:
|
||||
|
@ -163,6 +197,8 @@ Python::with_gil(|py| {
|
|||
println!("Python says: {}", hello.as_ref(py));
|
||||
drop(hello); // Memory released here.
|
||||
});
|
||||
# Ok(())
|
||||
# }
|
||||
```
|
||||
|
||||
We could also have used `Py::into_ref()`, which consumes `self`, instead of
|
||||
|
@ -172,8 +208,11 @@ that rather than being released immediately, the memory will not be released
|
|||
until the GIL is dropped.
|
||||
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
# use pyo3::types::PyString;
|
||||
# fn main() -> PyResult<()> {
|
||||
let hello: Py<PyString> = Python::with_gil(|py| {
|
||||
Py<PyString> = py.eval("\"Hello World!\"", None, None)?.extract())
|
||||
py.eval("\"Hello World!\"", None, None)?.extract()
|
||||
})?;
|
||||
// Do some stuff...
|
||||
// Now sometime later in the program:
|
||||
|
@ -183,4 +222,6 @@ Python::with_gil(|py| {
|
|||
// Do more stuff...
|
||||
// Memory released here at end of `with_gil()` closure.
|
||||
});
|
||||
# Ok(())
|
||||
# }
|
||||
```
|
||||
|
|
|
@ -7,7 +7,74 @@ For a detailed list of all changes, see the [CHANGELOG](changelog.md).
|
|||
|
||||
### Drop support for older technologies
|
||||
|
||||
PyO3 0.16 has increased minimum Rust version to 1.48 and minimum Python version to 3.7. This enables ore use of newer language features (enabling some of the other additions in 0.16) and simplifies maintenance of the project.
|
||||
PyO3 0.16 has increased minimum Rust version to 1.48 and minimum Python version to 3.7. This enables use of newer language features (enabling some of the other additions in 0.16) and simplifies maintenance of the project.
|
||||
|
||||
### `#[pyproto]` has been deprecated
|
||||
|
||||
In PyO3 0.15, the `#[pymethods]` attribute macro gained support for implementing "magic methods" such as `__str__` (aka "dunder" methods). This implementation was not quite finalized at the time, with a few edge cases to be decided upon. The existing `#[pyproto]` attribute macro was left untouched, because it covered these edge cases.
|
||||
|
||||
In PyO3 0.16, the `#[pymethods]` implementation has been completed and is now the preferred way to implement magic methods. To allow the PyO3 project to move forward, `#[pyproto]` has been deprecated (with expected removal in PyO3 0.18).
|
||||
|
||||
Migration from `#[pyproto]` to `#[pymethods]` is straightforward; copying the existing methods directly from the `#[pyproto]` trait implementation is all that is needed in most cases.
|
||||
|
||||
Before:
|
||||
|
||||
```rust,ignore
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::class::{PyBasicProtocol, PyIterProtocol};
|
||||
use pyo3::types::PyString;
|
||||
|
||||
#[pyclass]
|
||||
struct MyClass { }
|
||||
|
||||
#[pyproto]
|
||||
impl PyBasicProtocol for MyClass {
|
||||
fn __str__(&self) -> &'static [u8] {
|
||||
b"hello, world"
|
||||
}
|
||||
}
|
||||
|
||||
#[pyproto]
|
||||
impl PyIterProtocol for MyClass {
|
||||
fn __iter__(slf: PyRef<self>) -> PyResult<&PyAny> {
|
||||
PyString::new(slf.py(), "hello, world").iter()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
After
|
||||
|
||||
```rust,ignore
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::types::PyString;
|
||||
|
||||
#[pyclass]
|
||||
struct MyClass { }
|
||||
|
||||
#[pymethods]
|
||||
impl MyClass {
|
||||
fn __str__(&self) -> &'static [u8] {
|
||||
b"hello, world"
|
||||
}
|
||||
|
||||
fn __iter__(slf: PyRef<self>) -> PyResult<&PyAny> {
|
||||
PyString::new(slf.py(), "hello, world").iter()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Removed `PartialEq` for object wrappers
|
||||
|
||||
The Python object wrappers `Py` and `PyAny` had implementations of `PartialEq`
|
||||
so that `object_a == object_b` would compare the Python objects for pointer
|
||||
equality, which corresponds to the `is` operator, not the `==` operator in
|
||||
Python. This has been removed in favor of a new method: use
|
||||
`object_a.is(object_b)`. This also has the advantage of not requiring the same
|
||||
wrapper type for `object_a` and `object_b`; you can now directly compare a
|
||||
`Py<T>` with a `&PyAny` without having to convert.
|
||||
|
||||
To check for Python object equality (the Python `==` operator), use the new
|
||||
method `eq()`.
|
||||
|
||||
### Container magic methods now match Python behavior
|
||||
|
||||
|
@ -39,6 +106,55 @@ Because there is no such distinction from Python, implementing these methods wil
|
|||
|
||||
The PyO3 behavior in 0.16 has been changed to be closer to this Python behavior by default.
|
||||
|
||||
### `wrap_pymodule!` and `wrap_pyfunction!` now respect privacy correctly
|
||||
|
||||
Prior to PyO3 0.16 the `wrap_pymodule!` and `wrap_pyfunction!` macros could use modules and functions whose defining `fn` was not reachable according Rust privacy rules.
|
||||
|
||||
For example, the following code was legal before 0.16, but in 0.16 is rejected because the `wrap_pymodule!` macro cannot access the `private_submodule` function:
|
||||
|
||||
```rust,compile_fail
|
||||
mod foo {
|
||||
use pyo3::prelude::*;
|
||||
|
||||
#[pymodule]
|
||||
fn private_submodule(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
use pyo3::prelude::*;
|
||||
use foo::*;
|
||||
|
||||
#[pymodule]
|
||||
fn my_module(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||
m.add_wrapped(wrap_pymodule!(private_submodule))?;
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
To fix it, make the private submodule visible, e.g. with `pub` or `pub(crate)`.
|
||||
|
||||
```rust
|
||||
mod foo {
|
||||
use pyo3::prelude::*;
|
||||
|
||||
#[pymodule]
|
||||
pub(crate) fn private_submodule(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::wrap_pymodule;
|
||||
use foo::*;
|
||||
|
||||
#[pymodule]
|
||||
fn my_module(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||
m.add_wrapped(wrap_pymodule!(private_submodule))?;
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
## from 0.14.* to 0.15
|
||||
|
||||
### Changes in sequence indexing
|
||||
|
@ -230,7 +346,7 @@ Before:
|
|||
struct MyPyObjectWrapper(PyObject);
|
||||
|
||||
impl FromPy<MyPyObjectWrapper> for PyObject {
|
||||
fn from_py(other: MyPyObjectWrapper, _py: Python) -> Self {
|
||||
fn from_py(other: MyPyObjectWrapper, _py: Python<'_>) -> Self {
|
||||
other.0
|
||||
}
|
||||
}
|
||||
|
@ -242,7 +358,7 @@ After
|
|||
struct MyPyObjectWrapper(PyObject);
|
||||
|
||||
impl IntoPy<PyObject> for MyPyObjectWrapper {
|
||||
fn into_py(self, _py: Python) -> PyObject {
|
||||
fn into_py(self, _py: Python<'_>) -> PyObject {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
@ -560,10 +676,10 @@ let obj: &PyAny = create_obj();
|
|||
let obj_cell: &PyCell<MyClass> = obj.extract().unwrap();
|
||||
let obj_cloned: MyClass = obj.extract().unwrap(); // extracted by cloning the object
|
||||
{
|
||||
let obj_ref: PyRef<MyClass> = obj.extract().unwrap();
|
||||
let obj_ref: PyRef<'_, MyClass> = obj.extract().unwrap();
|
||||
// we need to drop obj_ref before we can extract a PyRefMut due to Rust's rules of references
|
||||
}
|
||||
let obj_ref_mut: PyRefMut<MyClass> = obj.extract().unwrap();
|
||||
let obj_ref_mut: PyRefMut<'_, MyClass> = obj.extract().unwrap();
|
||||
# })
|
||||
```
|
||||
|
||||
|
@ -593,7 +709,7 @@ impl PySequenceProtocol for ByteSequence {
|
|||
```
|
||||
|
||||
After:
|
||||
```rust
|
||||
```rust,ignore
|
||||
# use pyo3::prelude::*;
|
||||
# use pyo3::class::PySequenceProtocol;
|
||||
#[pyclass]
|
||||
|
|
|
@ -12,7 +12,7 @@ fn double(x: usize) -> usize {
|
|||
|
||||
/// This module is implemented in Rust.
|
||||
#[pymodule]
|
||||
fn my_extension(py: Python, m: &PyModule) -> PyResult<()> {
|
||||
fn my_extension(py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||
m.add_function(wrap_pyfunction!(double, m)?)?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ fn double(x: usize) -> usize {
|
|||
|
||||
#[pymodule]
|
||||
#[pyo3(name = "custom_name")]
|
||||
fn my_extension(py: Python, m: &PyModule) -> PyResult<()> {
|
||||
fn my_extension(py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||
m.add_function(wrap_pyfunction!(double, m)?)?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ references to the `PyModule` so that each module registers its own FFI code. For
|
|||
use pyo3::prelude::*;
|
||||
|
||||
#[pymodule]
|
||||
fn my_extension(py: Python, m: &PyModule) -> PyResult<()> {
|
||||
fn my_extension(py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||
dirutil::register(py, m)?;
|
||||
osutil::register(py, m)?;
|
||||
Ok(())
|
||||
|
@ -86,7 +86,7 @@ fn my_extension(py: Python, m: &PyModule) -> PyResult<()> {
|
|||
# mod dirutil {
|
||||
use pyo3::prelude::*;
|
||||
|
||||
pub(crate) fn register(py: Python, m: &PyModule) -> PyResult<()> {
|
||||
pub(crate) fn register(py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||
m.add_class::<SomeClass>()?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -99,7 +99,7 @@ struct SomeClass {/* ... */}
|
|||
# mod osutil {
|
||||
use pyo3::prelude::*;
|
||||
|
||||
pub(crate) fn register(py: Python, m: &PyModule) -> PyResult<()> {
|
||||
pub(crate) fn register(py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||
m.add_function(wrap_pyfunction!(determine_current_os, m)?)?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -120,7 +120,7 @@ use pyo3::prelude::*;
|
|||
use osutil::*;
|
||||
|
||||
#[pymodule]
|
||||
fn my_extension(py: Python, m: &PyModule) -> PyResult<()> {
|
||||
fn my_extension(py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||
m.add_function(wrap_pyfunction!(determine_current_os, m)?)?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -146,12 +146,12 @@ For example, you could define the modules `parent_module` and `parent_module.chi
|
|||
use pyo3::prelude::*;
|
||||
|
||||
#[pymodule]
|
||||
fn parent_module(py: Python, m: &PyModule) -> PyResult<()> {
|
||||
fn parent_module(py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||
register_child_module(py, m)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn register_child_module(py: Python, parent_module: &PyModule) -> PyResult<()> {
|
||||
fn register_child_module(py: Python<'_>, parent_module: &PyModule) -> PyResult<()> {
|
||||
let child_module = PyModule::new(py, "child_module")?;
|
||||
child_module.add_function(wrap_pyfunction!(func, child_module)?)?;
|
||||
parent_module.add_submodule(child_module)?;
|
||||
|
|
|
@ -23,7 +23,7 @@ fn search_sequential(contents: &str, needle: &str) -> usize {
|
|||
To enable parallel execution of this function, the [`Python::allow_threads`] method can be used to temporarily release the GIL, thus allowing other Python threads to run. We then have a function exposed to the Python runtime which calls `search_sequential` inside a closure passed to [`Python::allow_threads`] to enable true parallelism:
|
||||
```rust, ignore
|
||||
#[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))
|
||||
}
|
||||
```
|
||||
|
|
|
@ -165,7 +165,7 @@ quickly testing your Python extensions.
|
|||
|
||||
```rust
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::{PyCell, PyObjectProtocol, py_run};
|
||||
use pyo3::{PyCell, py_run};
|
||||
|
||||
# fn main() {
|
||||
#[pyclass]
|
||||
|
@ -179,10 +179,7 @@ impl UserData {
|
|||
fn as_tuple(&self) -> (u32, String) {
|
||||
(self.id, self.name.clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[pyproto]
|
||||
impl PyObjectProtocol for UserData {
|
||||
fn __repr__(&self) -> PyResult<String> {
|
||||
Ok(format!("User {}(id: {})", self.name, self.id))
|
||||
}
|
||||
|
|
|
@ -55,9 +55,9 @@ Here is an example of the PyList API:
|
|||
```rust,ignore
|
||||
impl PyList {
|
||||
|
||||
fn new(py: Python) -> PyList {...}
|
||||
fn new(py: Python<'_>) -> PyList {...}
|
||||
|
||||
fn get_item(&self, py: Python, index: isize) -> PyObject {...}
|
||||
fn get_item(&self, py: Python<'_>, index: isize) -> PyObject {...}
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -66,7 +66,7 @@ impl PyList {
|
|||
```rust,ignore
|
||||
impl PyList {
|
||||
|
||||
fn new(py: Python) -> &PyList {...}
|
||||
fn new(py: Python<'_>) -> &PyList {...}
|
||||
|
||||
fn get_item(&self, index: isize) -> &PyAny {...}
|
||||
}
|
||||
|
|
|
@ -133,7 +133,7 @@ struct UserModel {
|
|||
}
|
||||
|
||||
#[pymodule]
|
||||
fn trait_exposure(_py: Python, m: &PyModule) -> PyResult<()> {
|
||||
fn trait_exposure(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||
m.add_class::<UserModel>()?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -487,7 +487,7 @@ pub struct UserModel {
|
|||
}
|
||||
|
||||
#[pymodule]
|
||||
fn trait_exposure(_py: Python, m: &PyModule) -> PyResult<()> {
|
||||
fn trait_exposure(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||
m.add_class::<UserModel>()?;
|
||||
m.add_function(wrap_pyfunction!(solve_wrapper, m)?)?;
|
||||
Ok(())
|
||||
|
|
|
@ -103,9 +103,9 @@ 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()?;
|
||||
// To PyRef<'_, MyClass> or PyRefMut<'_, MyClass> with PyAny::extract
|
||||
let _: PyRef<'_, MyClass> = obj.extract()?;
|
||||
let _: PyRefMut<'_, MyClass> = obj.extract()?;
|
||||
# Ok(())
|
||||
# }).unwrap();
|
||||
```
|
||||
|
@ -207,11 +207,11 @@ let _: &PyCell<MyClass> = my_class.into_ref(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 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)?;
|
||||
// To PyRefMut<'_, MyClass> with Py::borrow_mut or Py::try_borrow_mut
|
||||
let _: PyRefMut<'_, MyClass> = my_class.try_borrow_mut(py)?;
|
||||
# Ok(())
|
||||
# }).unwrap();
|
||||
# });
|
||||
|
@ -241,12 +241,12 @@ so it also exposes all of the methods on `PyAny`.
|
|||
let cell: &PyCell<MyClass> = PyCell::new(py, MyClass { })?;
|
||||
|
||||
// To PyRef<T> with .borrow() or .try_borrow()
|
||||
let py_ref: PyRef<MyClass> = cell.try_borrow()?;
|
||||
let py_ref: PyRef<'_, MyClass> = cell.try_borrow()?;
|
||||
let _: &MyClass = &*py_ref;
|
||||
# drop(py_ref);
|
||||
|
||||
// To PyRefMut<T> with .borrow_mut() or .try_borrow_mut()
|
||||
let mut py_ref_mut: PyRefMut<MyClass> = cell.try_borrow_mut()?;
|
||||
let mut py_ref_mut: PyRefMut<'_, MyClass> = cell.try_borrow_mut()?;
|
||||
let _: &mut MyClass = &mut *py_ref_mut;
|
||||
# Ok(())
|
||||
# }).unwrap();
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
import time
|
||||
from glob import glob
|
||||
|
||||
import nox
|
||||
|
||||
nox.options.sessions = ["test", "clippy", "fmt"]
|
||||
|
||||
|
||||
@nox.session(venv_backend="none")
|
||||
def test(session: nox.Session):
|
||||
test_rust(session)
|
||||
test_py(session)
|
||||
|
||||
|
||||
@nox.session(name="test-rust", venv_backend="none")
|
||||
def test_rust(session: nox.Session):
|
||||
session.run("cargo", "test", external=True)
|
||||
session.run("cargo", "test", "--features=abi3", external=True)
|
||||
session.run("cargo", "test", "--features=full", external=True)
|
||||
session.run("cargo", "test", "--features=abi3 full", external=True)
|
||||
|
||||
|
||||
@nox.session(name="test-py", venv_backend="none")
|
||||
def test_py(session):
|
||||
session.run("nox", "-f", "pytests/noxfile.py", external=True)
|
||||
for example in glob("examples/*/noxfile.py"):
|
||||
session.run("nox", "-f", example, external=True)
|
||||
|
||||
|
||||
@nox.session
|
||||
def fmt(session: nox.Session):
|
||||
fmt_rust(session)
|
||||
fmt_py(session)
|
||||
|
||||
|
||||
@nox.session(name="fmt-rust", venv_backend="none")
|
||||
def fmt_rust(session: nox.Session):
|
||||
session.run("cargo", "fmt", "--all", "--check", external=True)
|
||||
|
||||
|
||||
@nox.session(name="fmt-py")
|
||||
def fmt_py(session: nox.Session):
|
||||
session.install("black==22.3.0")
|
||||
session.run("black", ".", "--check")
|
||||
|
||||
|
||||
@nox.session(venv_backend="none")
|
||||
def clippy(session: nox.Session) -> None:
|
||||
for feature_set in ["full", "abi3 full"]:
|
||||
session.run(
|
||||
"cargo",
|
||||
"clippy",
|
||||
f"--features={feature_set}",
|
||||
"--all-targets",
|
||||
"--workspace",
|
||||
"--",
|
||||
"--deny=warnings",
|
||||
external=True,
|
||||
)
|
||||
|
||||
|
||||
@nox.session(venv_backend="none")
|
||||
def publish(session: nox.Session) -> None:
|
||||
session.run(
|
||||
"cargo",
|
||||
"publish",
|
||||
"--manifest-path",
|
||||
"pyo3-build-config/Cargo.toml",
|
||||
external=True,
|
||||
)
|
||||
time.sleep(10)
|
||||
session.run(
|
||||
"cargo",
|
||||
"publish",
|
||||
"--manifest-path",
|
||||
"pyo3-macros-backend/Cargo.toml",
|
||||
external=True,
|
||||
)
|
||||
time.sleep(10)
|
||||
session.run(
|
||||
"cargo", "publish", "--manifest-path", "pyo3-macros/Cargo.toml", external=True
|
||||
)
|
||||
time.sleep(10)
|
||||
session.run(
|
||||
"cargo", "publish", "--manifest-path", "pyo3-ffi/Cargo.toml", external=True
|
||||
)
|
||||
time.sleep(10)
|
||||
session.run("cargo", "publish", external=True)
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "pyo3-build-config"
|
||||
version = "0.15.1"
|
||||
version = "0.16.2"
|
||||
description = "Build configuration for the PyO3 ecosystem"
|
||||
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
|
||||
keywords = ["pyo3", "python", "cpython", "ffi"]
|
||||
|
@ -25,3 +25,6 @@ abi3-py37 = ["abi3-py38"]
|
|||
abi3-py38 = ["abi3-py39"]
|
||||
abi3-py39 = ["abi3-py310"]
|
||||
abi3-py310 = ["abi3"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["resolve-config"]
|
||||
|
|
|
@ -2,8 +2,7 @@
|
|||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
macro_rules! bail {
|
||||
($msg: expr) => { return Err($msg.into()) };
|
||||
($fmt: literal $($args: tt)+) => { return Err(format!($fmt $($args)+).into()) };
|
||||
($($args: tt)+) => { return Err(format!($($args)+).into()) };
|
||||
}
|
||||
|
||||
/// A simple macro for checking a condition. Resembles anyhow::ensure.
|
||||
|
|
|
@ -8,6 +8,7 @@ use std::{
|
|||
io::{BufRead, BufReader, Read, Write},
|
||||
path::{Path, PathBuf},
|
||||
process::{Command, Stdio},
|
||||
str,
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
|
@ -19,8 +20,9 @@ use crate::{
|
|||
|
||||
/// Minimum Python version PyO3 supports.
|
||||
const MINIMUM_SUPPORTED_VERSION: PythonVersion = PythonVersion { major: 3, minor: 7 };
|
||||
|
||||
/// Maximum Python version that can be used as minimum required Python version with abi3.
|
||||
const ABI3_MAX_MINOR: u8 = 9;
|
||||
const ABI3_MAX_MINOR: u8 = 10;
|
||||
|
||||
/// Gets an environment variable owned by cargo.
|
||||
///
|
||||
|
@ -153,7 +155,7 @@ impl InterpreterConfig {
|
|||
}
|
||||
|
||||
for flag in &self.build_flags.0 {
|
||||
println!("cargo:rustc-cfg=py_sys_config=\"{}\"", flag)
|
||||
println!("cargo:rustc-cfg=py_sys_config=\"{}\"", flag);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -188,7 +190,7 @@ def print_if_set(varname, value):
|
|||
print(varname, value)
|
||||
|
||||
# Windows always uses shared linking
|
||||
WINDOWS = hasattr(platform, "win32_ver")
|
||||
WINDOWS = platform.system() == "Windows"
|
||||
|
||||
# macOS framework packages use shared linking
|
||||
FRAMEWORK = bool(get_config_var("PYTHONFRAMEWORK"))
|
||||
|
@ -299,6 +301,11 @@ print("mingw", get_platform().startswith("mingw"))
|
|||
Some("0") | Some("false") | Some("False") => false,
|
||||
_ => bail!("expected a bool (1/true/True or 0/false/False) for Py_ENABLE_SHARED"),
|
||||
};
|
||||
// macOS framework packages use shared linking (PYTHONFRAMEWORK is the framework name, hence the empty check)
|
||||
let framework = match sysconfigdata.get_value("PYTHONFRAMEWORK") {
|
||||
Some(s) => !s.is_empty(),
|
||||
_ => false,
|
||||
};
|
||||
let lib_dir = get_key!(sysconfigdata, "LIBDIR").ok().map(str::to_string);
|
||||
let lib_name = Some(default_lib_name_unix(
|
||||
version,
|
||||
|
@ -313,7 +320,7 @@ print("mingw", get_platform().startswith("mingw"))
|
|||
Ok(InterpreterConfig {
|
||||
implementation,
|
||||
version,
|
||||
shared,
|
||||
shared: shared || framework,
|
||||
abi3: is_abi3(),
|
||||
lib_dir,
|
||||
lib_name,
|
||||
|
@ -334,6 +341,12 @@ print("mingw", get_platform().startswith("mingw"))
|
|||
InterpreterConfig::from_reader(reader)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn from_cargo_dep_env() -> Option<Result<Self>> {
|
||||
cargo_env_var("DEP_PYTHON_PYO3_CONFIG")
|
||||
.map(|buf| InterpreterConfig::from_reader(buf.replace("\\n", "\n").as_bytes()))
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn from_reader(reader: impl Read) -> Result<Self> {
|
||||
let reader = BufReader::new(reader);
|
||||
|
@ -414,6 +427,29 @@ print("mingw", get_platform().startswith("mingw"))
|
|||
})
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
/// Serialize the `InterpreterConfig` and print it to the environment for Cargo to pass along
|
||||
/// to dependent packages during build time.
|
||||
///
|
||||
/// NB: writing to the cargo environment requires the
|
||||
/// [`links`](https://doc.rust-lang.org/cargo/reference/build-scripts.html#the-links-manifest-key)
|
||||
/// manifest key to be set. In this case that means this is called by the `pyo3-ffi` crate and
|
||||
/// available for dependent package build scripts in `DEP_PYTHON_PYO3_CONFIG`. See
|
||||
/// documentation for the
|
||||
/// [`DEP_<name>_<key>`](https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts)
|
||||
/// environment variable.
|
||||
pub fn to_cargo_dep_env(&self) -> Result<()> {
|
||||
let mut buf = Vec::new();
|
||||
self.to_writer(&mut buf)?;
|
||||
// escape newlines in env var
|
||||
if let Ok(config) = str::from_utf8(&buf) {
|
||||
println!("cargo:PYO3_CONFIG={}", config.replace('\n', "\\n"));
|
||||
} else {
|
||||
bail!("unable to emit interpreter config to link env for downstream use");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn to_writer(&self, mut writer: impl Write) -> Result<()> {
|
||||
macro_rules! write_line {
|
||||
|
@ -456,6 +492,38 @@ print("mingw", get_platform().startswith("mingw"))
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Run a python script using the [`InterpreterConfig::executable`].
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function will panic if the [`executable`](InterpreterConfig::executable) is `None`.
|
||||
pub fn run_python_script(&self, script: &str) -> Result<String> {
|
||||
run_python_script_with_envs(
|
||||
Path::new(self.executable.as_ref().expect("no interpreter executable")),
|
||||
script,
|
||||
std::iter::empty::<(&str, &str)>(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Run a python script using the [`InterpreterConfig::executable`] with additional
|
||||
/// environment variables (e.g. PYTHONPATH) set.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function will panic if the [`executable`](InterpreterConfig::executable) is `None`.
|
||||
pub fn run_python_script_with_envs<I, K, V>(&self, script: &str, envs: I) -> Result<String>
|
||||
where
|
||||
I: IntoIterator<Item = (K, V)>,
|
||||
K: AsRef<OsStr>,
|
||||
V: AsRef<OsStr>,
|
||||
{
|
||||
run_python_script_with_envs(
|
||||
Path::new(self.executable.as_ref().expect("no interpreter executable")),
|
||||
script,
|
||||
envs,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
|
@ -540,6 +608,54 @@ fn is_abi3() -> bool {
|
|||
cargo_env_var("CARGO_FEATURE_ABI3").is_some()
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct TargetInfo {
|
||||
/// The `arch` component of the compilation target triple.
|
||||
///
|
||||
/// e.g. x86_64, i386, arm, thumb, mips, etc.
|
||||
arch: String,
|
||||
|
||||
/// The `vendor` component of the compilation target triple.
|
||||
///
|
||||
/// e.g. apple, pc, unknown, etc.
|
||||
vendor: String,
|
||||
|
||||
/// The `os` component of the compilation target triple.
|
||||
///
|
||||
/// e.g. darwin, freebsd, linux, windows, etc.
|
||||
os: String,
|
||||
}
|
||||
|
||||
impl TargetInfo {
|
||||
fn from_cargo_env() -> Result<Self> {
|
||||
Ok(Self {
|
||||
arch: cargo_env_var("CARGO_CFG_TARGET_ARCH")
|
||||
.ok_or("expected CARGO_CFG_TARGET_ARCH env var")?,
|
||||
vendor: cargo_env_var("CARGO_CFG_TARGET_VENDOR")
|
||||
.ok_or("expected CARGO_CFG_TARGET_VENDOR env var")?,
|
||||
os: cargo_env_var("CARGO_CFG_TARGET_OS")
|
||||
.ok_or("expected CARGO_CFG_TARGET_OS env var")?,
|
||||
})
|
||||
}
|
||||
|
||||
fn to_target_triple(&self) -> String {
|
||||
format!(
|
||||
"{}-{}-{}",
|
||||
if self.arch == "x86" {
|
||||
"i686"
|
||||
} else {
|
||||
&self.arch
|
||||
},
|
||||
self.vendor,
|
||||
if self.os == "macos" {
|
||||
"darwin"
|
||||
} else {
|
||||
&self.os
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration needed by PyO3 to cross-compile for a target platform.
|
||||
///
|
||||
/// Usually this is collected from the environment (i.e. `PYO3_CROSS_*` and `CARGO_CFG_TARGET_*`)
|
||||
|
@ -552,51 +668,55 @@ pub struct CrossCompileConfig {
|
|||
/// The version of the Python library to link against.
|
||||
version: Option<PythonVersion>,
|
||||
|
||||
/// The `arch` component of the compilaton target triple.
|
||||
///
|
||||
/// e.g. x86_64, i386, arm, thumb, mips, etc.
|
||||
arch: String,
|
||||
|
||||
/// The `vendor` component of the compilaton target triple.
|
||||
///
|
||||
/// e.g. apple, pc, unknown, etc.
|
||||
vendor: String,
|
||||
|
||||
/// The `os` component of the compilaton target triple.
|
||||
///
|
||||
/// e.g. darwin, freebsd, linux, windows, etc.
|
||||
os: String,
|
||||
/// The target information
|
||||
target_info: TargetInfo,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn any_cross_compiling_env_vars_set() -> bool {
|
||||
env::var_os("PYO3_CROSS").is_some()
|
||||
|| env::var_os("PYO3_CROSS_LIB_DIR").is_some()
|
||||
|| env::var_os("PYO3_CROSS_PYTHON_VERSION").is_some()
|
||||
impl CrossCompileConfig {
|
||||
fn from_env_vars(env_vars: CrossCompileEnvVars, target_info: TargetInfo) -> Result<Self> {
|
||||
Ok(CrossCompileConfig {
|
||||
lib_dir: env_vars
|
||||
.pyo3_cross_lib_dir
|
||||
.ok_or(
|
||||
"The PYO3_CROSS_LIB_DIR environment variable must be set when cross-compiling",
|
||||
)?
|
||||
.into(),
|
||||
target_info,
|
||||
version: env_vars
|
||||
.pyo3_cross_python_version
|
||||
.map(|os_string| {
|
||||
let utf8_str = os_string
|
||||
.to_str()
|
||||
.ok_or("PYO3_CROSS_PYTHON_VERSION is not valid utf-8.")?;
|
||||
utf8_str
|
||||
.parse()
|
||||
.context("failed to parse PYO3_CROSS_PYTHON_VERSION")
|
||||
})
|
||||
.transpose()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn cross_compiling_from_cargo_env() -> Result<Option<CrossCompileConfig>> {
|
||||
let host = cargo_env_var("HOST").ok_or("expected HOST env var")?;
|
||||
let target = cargo_env_var("TARGET").ok_or("expected TARGET env var")?;
|
||||
pub(crate) struct CrossCompileEnvVars {
|
||||
pyo3_cross: Option<OsString>,
|
||||
pyo3_cross_lib_dir: Option<OsString>,
|
||||
pyo3_cross_python_version: Option<OsString>,
|
||||
}
|
||||
|
||||
if host == target {
|
||||
// Definitely not cross compiling if the host matches the target
|
||||
return Ok(None);
|
||||
impl CrossCompileEnvVars {
|
||||
pub fn any(&self) -> bool {
|
||||
self.pyo3_cross.is_some()
|
||||
|| self.pyo3_cross_lib_dir.is_some()
|
||||
|| self.pyo3_cross_python_version.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
if target == "i686-pc-windows-msvc" && host == "x86_64-pc-windows-msvc" {
|
||||
// Not cross-compiling to compile for 32-bit Python from windows 64-bit
|
||||
return Ok(None);
|
||||
pub(crate) fn cross_compile_env_vars() -> CrossCompileEnvVars {
|
||||
CrossCompileEnvVars {
|
||||
pyo3_cross: env::var_os("PYO3_CROSS"),
|
||||
pyo3_cross_lib_dir: env::var_os("PYO3_CROSS_LIB_DIR"),
|
||||
pyo3_cross_python_version: env::var_os("PYO3_CROSS_PYTHON_VERSION"),
|
||||
}
|
||||
|
||||
let target_arch =
|
||||
cargo_env_var("CARGO_CFG_TARGET_ARCH").ok_or("expected CARGO_CFG_TARGET_ARCH env var")?;
|
||||
let target_vendor = cargo_env_var("CARGO_CFG_TARGET_VENDOR")
|
||||
.ok_or("expected CARGO_CFG_TARGET_VENDOR env var")?;
|
||||
let target_os =
|
||||
cargo_env_var("CARGO_CFG_TARGET_OS").ok_or("expected CARGO_CFG_TARGET_OS env var")?;
|
||||
|
||||
cross_compiling(&host, &target_arch, &target_vendor, &target_os)
|
||||
}
|
||||
|
||||
/// Detect whether we are cross compiling and return an assembled CrossCompileConfig if so.
|
||||
|
@ -619,53 +739,33 @@ pub fn cross_compiling(
|
|||
target_vendor: &str,
|
||||
target_os: &str,
|
||||
) -> Result<Option<CrossCompileConfig>> {
|
||||
let cross = env_var("PYO3_CROSS");
|
||||
let cross_lib_dir = env_var("PYO3_CROSS_LIB_DIR");
|
||||
let cross_python_version = env_var("PYO3_CROSS_PYTHON_VERSION");
|
||||
let env_vars = cross_compile_env_vars();
|
||||
|
||||
let target_triple = format!("{}-{}-{}", target_arch, target_vendor, target_os);
|
||||
let target_info = TargetInfo {
|
||||
arch: target_arch.to_owned(),
|
||||
vendor: target_vendor.to_owned(),
|
||||
os: target_os.to_owned(),
|
||||
};
|
||||
|
||||
if cross.is_none() && cross_lib_dir.is_none() && cross_python_version.is_none() {
|
||||
// No cross-compiling environment variables set; try to determine if this is a known case
|
||||
// which is not cross-compilation.
|
||||
|
||||
if target_triple == "x86_64-apple-darwin" && host == "aarch64-apple-darwin" {
|
||||
// Not cross-compiling to compile for x86-64 Python from macOS arm64
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if target_triple == "aarch64-apple-darwin" && host == "x86_64-apple-darwin" {
|
||||
// Not cross-compiling to compile for arm64 Python from macOS x86_64
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if host.starts_with(&target_triple) {
|
||||
// Not cross-compiling if arch-vendor-os is all the same
|
||||
// e.g. x86_64-unknown-linux-musl on x86_64-unknown-linux-gnu host
|
||||
return Ok(None);
|
||||
}
|
||||
if !env_vars.any() && is_not_cross_compiling(host, &target_info) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// At this point we assume that we are cross compiling.
|
||||
CrossCompileConfig::from_env_vars(env_vars, target_info).map(Some)
|
||||
}
|
||||
|
||||
Ok(Some(CrossCompileConfig {
|
||||
lib_dir: cross_lib_dir
|
||||
.ok_or("The PYO3_CROSS_LIB_DIR environment variable must be set when cross-compiling")?
|
||||
.into(),
|
||||
arch: target_arch.into(),
|
||||
vendor: target_vendor.into(),
|
||||
os: target_os.into(),
|
||||
version: cross_python_version
|
||||
.map(|os_string| {
|
||||
let utf8_str = os_string
|
||||
.to_str()
|
||||
.ok_or("PYO3_CROSS_PYTHON_VERSION is not valid utf-8.")?;
|
||||
utf8_str
|
||||
.parse()
|
||||
.context("failed to parse PYO3_CROSS_PYTHON_VERSION")
|
||||
})
|
||||
.transpose()?,
|
||||
}))
|
||||
fn is_not_cross_compiling(host: &str, target_info: &TargetInfo) -> bool {
|
||||
let target_triple = target_info.to_target_triple();
|
||||
// Not cross-compiling if arch-vendor-os is all the same
|
||||
// e.g. x86_64-unknown-linux-musl on x86_64-unknown-linux-gnu host
|
||||
// x86_64-pc-windows-gnu on x86_64-pc-windows-msvc host
|
||||
host.starts_with(&target_triple)
|
||||
// Not cross-compiling to compile for 32-bit Python from windows 64-bit
|
||||
|| (target_triple == "i686-pc-windows" && host.starts_with("x86_64-pc-windows"))
|
||||
// Not cross-compiling to compile for x86-64 Python from macOS arm64
|
||||
|| (target_triple == "x86_64-apple-darwin" && host == "aarch64-apple-darwin")
|
||||
// Not cross-compiling to compile for arm64 Python from macOS x86_64
|
||||
|| (target_triple == "aarch64-apple-darwin" && host == "x86_64-apple-darwin")
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
|
@ -755,7 +855,7 @@ impl BuildFlags {
|
|||
let mut script = String::from("import sysconfig\n");
|
||||
script.push_str("config = sysconfig.get_config_vars()\n");
|
||||
|
||||
for k in BuildFlags::ALL.iter() {
|
||||
for k in &BuildFlags::ALL {
|
||||
script.push_str(&format!("print(config.get('{}', '0'))\n", k));
|
||||
}
|
||||
|
||||
|
@ -793,10 +893,10 @@ impl Display for BuildFlags {
|
|||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut first = true;
|
||||
for flag in &self.0 {
|
||||
if !first {
|
||||
write!(f, ",")?;
|
||||
} else {
|
||||
if first {
|
||||
first = false;
|
||||
} else {
|
||||
write!(f, ",")?;
|
||||
}
|
||||
write!(f, "{}", flag)?;
|
||||
}
|
||||
|
@ -991,15 +1091,15 @@ fn search_lib_dir(path: impl AsRef<Path>, cross: &CrossCompileConfig) -> Vec<Pat
|
|||
search_lib_dir(f.path(), cross)
|
||||
} else if file_name.starts_with("lib.") {
|
||||
// check if right target os
|
||||
if !file_name.contains(if cross.os == "android" {
|
||||
if !file_name.contains(if cross.target_info.os == "android" {
|
||||
"linux"
|
||||
} else {
|
||||
&cross.os
|
||||
&cross.target_info.os
|
||||
}) {
|
||||
continue;
|
||||
}
|
||||
// Check if right arch
|
||||
if !file_name.contains(&cross.arch) {
|
||||
if !file_name.contains(&cross.target_info.arch) {
|
||||
continue;
|
||||
}
|
||||
search_lib_dir(f.path(), cross)
|
||||
|
@ -1024,7 +1124,7 @@ fn search_lib_dir(path: impl AsRef<Path>, cross: &CrossCompileConfig) -> Vec<Pat
|
|||
if sysconfig_paths.len() > 1 {
|
||||
let temp = sysconfig_paths
|
||||
.iter()
|
||||
.filter(|p| p.to_string_lossy().contains(&cross.arch))
|
||||
.filter(|p| p.to_string_lossy().contains(&cross.target_info.arch))
|
||||
.cloned()
|
||||
.collect::<Vec<PathBuf>>();
|
||||
if !temp.is_empty() {
|
||||
|
@ -1040,7 +1140,7 @@ fn search_lib_dir(path: impl AsRef<Path>, cross: &CrossCompileConfig) -> Vec<Pat
|
|||
/// first find sysconfigdata file which follows the pattern [`_sysconfigdata_{abi}_{platform}_{multiarch}`][1]
|
||||
///
|
||||
/// [1]: https://github.com/python/cpython/blob/3.8/Lib/sysconfig.py#L348
|
||||
fn load_cross_compile_from_sysconfigdata(
|
||||
fn cross_compile_from_sysconfigdata(
|
||||
cross_compile_config: CrossCompileConfig,
|
||||
) -> Result<InterpreterConfig> {
|
||||
let sysconfigdata_path = find_sysconfigdata(&cross_compile_config)?;
|
||||
|
@ -1081,11 +1181,11 @@ fn load_cross_compile_config(
|
|||
) -> Result<InterpreterConfig> {
|
||||
match cargo_env_var("CARGO_CFG_TARGET_FAMILY") {
|
||||
// Configure for unix platforms using the sysconfigdata file
|
||||
Some(os) if os == "unix" => load_cross_compile_from_sysconfigdata(cross_compile_config),
|
||||
Some(os) if os == "unix" => cross_compile_from_sysconfigdata(cross_compile_config),
|
||||
// Use hardcoded interpreter config when targeting Windows
|
||||
Some(os) if os == "windows" => windows_hardcoded_cross_compile(cross_compile_config),
|
||||
// sysconfigdata works fine on wasm/wasi
|
||||
Some(os) if os == "wasm" => load_cross_compile_from_sysconfigdata(cross_compile_config),
|
||||
Some(os) if os == "wasm" => cross_compile_from_sysconfigdata(cross_compile_config),
|
||||
// Waiting for users to tell us what they expect on their target platform
|
||||
Some(os) => bail!(
|
||||
"Unknown target OS family for cross-compilation: {:?}.\n\
|
||||
|
@ -1095,7 +1195,7 @@ fn load_cross_compile_config(
|
|||
os
|
||||
),
|
||||
// Unknown os family - try to do something useful
|
||||
None => load_cross_compile_from_sysconfigdata(cross_compile_config),
|
||||
None => cross_compile_from_sysconfigdata(cross_compile_config),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1131,14 +1231,35 @@ fn default_lib_name_unix(
|
|||
Some(ld_version) => format!("python{}", ld_version),
|
||||
None => format!("python{}.{}", version.major, version.minor),
|
||||
},
|
||||
PythonImplementation::PyPy => format!("pypy{}-c", version.major),
|
||||
PythonImplementation::PyPy => {
|
||||
if version >= (PythonVersion { major: 3, minor: 9 }) {
|
||||
match ld_version {
|
||||
Some(ld_version) => format!("pypy{}-c", ld_version),
|
||||
None => format!("pypy{}.{}-c", version.major, version.minor),
|
||||
}
|
||||
} else {
|
||||
format!("pypy{}-c", version.major)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Run a python script using the specified interpreter binary.
|
||||
fn run_python_script(interpreter: &Path, script: &str) -> Result<String> {
|
||||
run_python_script_with_envs(interpreter, script, std::iter::empty::<(&str, &str)>())
|
||||
}
|
||||
|
||||
/// Run a python script using the specified interpreter binary with additional environment
|
||||
/// variables (e.g. PYTHONPATH) set.
|
||||
fn run_python_script_with_envs<I, K, V>(interpreter: &Path, script: &str, envs: I) -> Result<String>
|
||||
where
|
||||
I: IntoIterator<Item = (K, V)>,
|
||||
K: AsRef<OsStr>,
|
||||
V: AsRef<OsStr>,
|
||||
{
|
||||
let out = Command::new(interpreter)
|
||||
.env("PYTHONIOENCODING", "utf-8")
|
||||
.envs(envs)
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::inherit())
|
||||
|
@ -1237,6 +1358,11 @@ fn fixup_config_for_abi3(
|
|||
config: &mut InterpreterConfig,
|
||||
abi3_version: Option<PythonVersion>,
|
||||
) -> Result<()> {
|
||||
// PyPy doesn't support abi3; don't adjust the version
|
||||
if config.implementation.is_pypy() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Some(version) = abi3_version {
|
||||
ensure!(
|
||||
version <= config.version,
|
||||
|
@ -1257,13 +1383,30 @@ fn fixup_config_for_abi3(
|
|||
/// This must be called from PyO3's build script, because it relies on environment variables such as
|
||||
/// CARGO_CFG_TARGET_OS which aren't available at any other time.
|
||||
pub fn make_cross_compile_config() -> Result<Option<InterpreterConfig>> {
|
||||
let mut interpreter_config = if let Some(paths) = cross_compiling_from_cargo_env()? {
|
||||
load_cross_compile_config(paths)?
|
||||
let env_vars = cross_compile_env_vars();
|
||||
|
||||
let host = cargo_env_var("HOST").ok_or("expected HOST env var")?;
|
||||
let target = cargo_env_var("TARGET").ok_or("expected TARGET env var")?;
|
||||
|
||||
let target_info = TargetInfo::from_cargo_env()?;
|
||||
|
||||
let interpreter_config = if env_vars.any() {
|
||||
let cross_config = CrossCompileConfig::from_env_vars(env_vars, target_info)?;
|
||||
let mut interpreter_config = load_cross_compile_config(cross_config)?;
|
||||
fixup_config_for_abi3(&mut interpreter_config, get_abi3_version())?;
|
||||
Some(interpreter_config)
|
||||
} else {
|
||||
return Ok(None);
|
||||
ensure!(
|
||||
host == target || is_not_cross_compiling(&host, &target_info),
|
||||
"PyO3 detected compile host {host} and build target {target}, but none of PYO3_CROSS, PYO3_CROSS_LIB_DIR \
|
||||
or PYO3_CROSS_PYTHON_VERSION environment variables are set.",
|
||||
host=host,
|
||||
target=target,
|
||||
);
|
||||
None
|
||||
};
|
||||
fixup_config_for_abi3(&mut interpreter_config, get_abi3_version())?;
|
||||
Ok(Some(interpreter_config))
|
||||
|
||||
Ok(interpreter_config)
|
||||
}
|
||||
|
||||
/// Generates an interpreter config which will be hard-coded into the pyo3-build-config crate.
|
||||
|
@ -1467,14 +1610,71 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_from_sysconfigdata_framework() {
|
||||
let mut sysconfigdata = Sysconfigdata::new();
|
||||
sysconfigdata.insert("SOABI", "cpython-37m-x86_64-linux-gnu");
|
||||
sysconfigdata.insert("VERSION", "3.7");
|
||||
// PYTHONFRAMEWORK should override Py_ENABLE_SHARED
|
||||
sysconfigdata.insert("Py_ENABLE_SHARED", "0");
|
||||
sysconfigdata.insert("PYTHONFRAMEWORK", "Python");
|
||||
sysconfigdata.insert("LIBDIR", "/usr/lib");
|
||||
sysconfigdata.insert("LDVERSION", "3.7m");
|
||||
sysconfigdata.insert("SIZEOF_VOID_P", "8");
|
||||
assert_eq!(
|
||||
InterpreterConfig::from_sysconfigdata(&sysconfigdata).unwrap(),
|
||||
InterpreterConfig {
|
||||
abi3: false,
|
||||
build_flags: BuildFlags::from_sysconfigdata(&sysconfigdata),
|
||||
pointer_width: Some(64),
|
||||
executable: None,
|
||||
implementation: PythonImplementation::CPython,
|
||||
lib_dir: Some("/usr/lib".into()),
|
||||
lib_name: Some("python3.7m".into()),
|
||||
shared: true,
|
||||
version: PythonVersion::PY37,
|
||||
suppress_build_script_link_lines: false,
|
||||
extra_build_script_lines: vec![],
|
||||
}
|
||||
);
|
||||
|
||||
sysconfigdata = Sysconfigdata::new();
|
||||
sysconfigdata.insert("SOABI", "cpython-37m-x86_64-linux-gnu");
|
||||
sysconfigdata.insert("VERSION", "3.7");
|
||||
// An empty PYTHONFRAMEWORK means it is not a framework
|
||||
sysconfigdata.insert("Py_ENABLE_SHARED", "0");
|
||||
sysconfigdata.insert("PYTHONFRAMEWORK", "");
|
||||
sysconfigdata.insert("LIBDIR", "/usr/lib");
|
||||
sysconfigdata.insert("LDVERSION", "3.7m");
|
||||
sysconfigdata.insert("SIZEOF_VOID_P", "8");
|
||||
assert_eq!(
|
||||
InterpreterConfig::from_sysconfigdata(&sysconfigdata).unwrap(),
|
||||
InterpreterConfig {
|
||||
abi3: false,
|
||||
build_flags: BuildFlags::from_sysconfigdata(&sysconfigdata),
|
||||
pointer_width: Some(64),
|
||||
executable: None,
|
||||
implementation: PythonImplementation::CPython,
|
||||
lib_dir: Some("/usr/lib".into()),
|
||||
lib_name: Some("python3.7m".into()),
|
||||
shared: false,
|
||||
version: PythonVersion::PY37,
|
||||
suppress_build_script_link_lines: false,
|
||||
extra_build_script_lines: vec![],
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn windows_hardcoded_cross_compile() {
|
||||
let cross_config = CrossCompileConfig {
|
||||
lib_dir: "C:\\some\\path".into(),
|
||||
version: Some(PythonVersion { major: 3, minor: 7 }),
|
||||
os: "os".into(),
|
||||
arch: "arch".into(),
|
||||
vendor: "vendor".into(),
|
||||
target_info: TargetInfo {
|
||||
os: "os".into(),
|
||||
arch: "arch".into(),
|
||||
vendor: "vendor".into(),
|
||||
},
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
|
@ -1567,11 +1767,17 @@ mod tests {
|
|||
"python3.7md",
|
||||
);
|
||||
|
||||
// PyPy ignores ldversion
|
||||
// PyPy 3.7 ignores ldversion
|
||||
assert_eq!(
|
||||
super::default_lib_name_unix(PythonVersion { major: 3, minor: 9 }, PyPy, Some("3.7md")),
|
||||
super::default_lib_name_unix(PythonVersion { major: 3, minor: 7 }, PyPy, Some("3.7md")),
|
||||
"pypy3-c",
|
||||
);
|
||||
|
||||
// PyPy 3.9 includes ldversion
|
||||
assert_eq!(
|
||||
super::default_lib_name_unix(PythonVersion { major: 3, minor: 9 }, PyPy, Some("3.9d")),
|
||||
"pypy3.9d-c",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -1640,9 +1846,11 @@ mod tests {
|
|||
let cross = CrossCompileConfig {
|
||||
lib_dir: lib_dir.into(),
|
||||
version: Some(interpreter_config.version),
|
||||
arch: "x86_64".into(),
|
||||
vendor: "unknown".into(),
|
||||
os: "linux".into(),
|
||||
target_info: TargetInfo {
|
||||
arch: "x86_64".into(),
|
||||
vendor: "unknown".into(),
|
||||
os: "linux".into(),
|
||||
},
|
||||
};
|
||||
|
||||
let sysconfigdata_path = match find_sysconfigdata(&cross) {
|
||||
|
@ -1715,4 +1923,29 @@ mod tests {
|
|||
.is_none()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_run_python_script() {
|
||||
// as above, this should be okay in CI where Python is presumed installed
|
||||
let interpreter = make_interpreter_config()
|
||||
.expect("could not get InterpreterConfig from installed interpreter");
|
||||
let out = interpreter
|
||||
.run_python_script("print(2 + 2)")
|
||||
.expect("failed to run Python script");
|
||||
assert_eq!(out.trim_end(), "4");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_run_python_script_with_envs() {
|
||||
// as above, this should be okay in CI where Python is presumed installed
|
||||
let interpreter = make_interpreter_config()
|
||||
.expect("could not get InterpreterConfig from installed interpreter");
|
||||
let out = interpreter
|
||||
.run_python_script_with_envs(
|
||||
"import os; print(os.getenv('PYO3_TEST'))",
|
||||
vec![("PYO3_TEST", "42")],
|
||||
)
|
||||
.expect("failed to run Python script");
|
||||
assert_eq!(out.trim_end(), "42");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,11 +5,14 @@
|
|||
//!
|
||||
//! It used internally by the PyO3 crate's build script to apply the same configuration.
|
||||
|
||||
#![warn(elided_lifetimes_in_paths, unused_lifetimes)]
|
||||
|
||||
mod errors;
|
||||
mod impl_;
|
||||
|
||||
#[cfg(feature = "resolve-config")]
|
||||
use std::io::Cursor;
|
||||
use std::{env, process::Command};
|
||||
|
||||
#[cfg(feature = "resolve-config")]
|
||||
use once_cell::sync::OnceCell;
|
||||
|
@ -60,21 +63,22 @@ fn _add_extension_module_link_args(target_os: &str, mut writer: impl std::io::Wr
|
|||
/// Loads the configuration determined from the build environment.
|
||||
///
|
||||
/// Because this will never change in a given compilation run, this is cached in a `once_cell`.
|
||||
#[doc(hidden)]
|
||||
#[cfg(feature = "resolve-config")]
|
||||
pub fn get() -> &'static InterpreterConfig {
|
||||
static CONFIG: OnceCell<InterpreterConfig> = OnceCell::new();
|
||||
CONFIG.get_or_init(|| {
|
||||
if !CONFIG_FILE.is_empty() {
|
||||
if let Some(interpreter_config) = InterpreterConfig::from_cargo_dep_env() {
|
||||
interpreter_config
|
||||
} else if !CONFIG_FILE.is_empty() {
|
||||
InterpreterConfig::from_reader(Cursor::new(CONFIG_FILE))
|
||||
} else if !ABI3_CONFIG.is_empty() {
|
||||
Ok(abi3_config())
|
||||
} else if impl_::any_cross_compiling_env_vars_set() {
|
||||
} else if impl_::cross_compile_env_vars().any() {
|
||||
InterpreterConfig::from_path(DEFAULT_CROSS_COMPILE_CONFIG_PATH)
|
||||
} else {
|
||||
InterpreterConfig::from_reader(Cursor::new(HOST_CONFIG))
|
||||
}
|
||||
.expect("failed to parse PyO3 config file")
|
||||
.expect("failed to parse PyO3 config")
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -115,6 +119,36 @@ fn abi3_config() -> InterpreterConfig {
|
|||
interpreter_config
|
||||
}
|
||||
|
||||
/// Use certain features if we detect the compiler being used supports them.
|
||||
///
|
||||
/// Features may be removed or added as MSRV gets bumped or new features become available,
|
||||
/// so this function is unstable.
|
||||
#[doc(hidden)]
|
||||
pub fn print_feature_cfgs() {
|
||||
fn rustc_minor_version() -> Option<u32> {
|
||||
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()
|
||||
}
|
||||
|
||||
let rustc_minor_version = rustc_minor_version().unwrap_or(0);
|
||||
|
||||
// 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");
|
||||
}
|
||||
}
|
||||
|
||||
/// Private exports used in PyO3's build.rs
|
||||
///
|
||||
/// Please don't use these - they could change at any time.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "pyo3-ffi"
|
||||
version = "0.15.1"
|
||||
version = "0.16.2"
|
||||
description = "Python-API bindings for the PyO3 ecosystem"
|
||||
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
|
||||
keywords = ["pyo3", "python", "cpython", "ffi"]
|
||||
|
@ -9,6 +9,7 @@ repository = "https://github.com/pyo3/pyo3"
|
|||
categories = ["api-bindings", "development-tools::ffi"]
|
||||
license = "Apache-2.0"
|
||||
edition = "2018"
|
||||
links = "python"
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2.62"
|
||||
|
@ -34,6 +35,4 @@ abi3-py310 = ["abi3", "pyo3-build-config/abi3-py310"]
|
|||
|
||||
|
||||
[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"] }
|
||||
|
|
|
@ -44,8 +44,8 @@ features = ["extension-module"]
|
|||
|
||||
**`src/lib.rs`**
|
||||
```rust
|
||||
use std::mem::transmute;
|
||||
use std::os::raw::c_char;
|
||||
use std::ptr;
|
||||
|
||||
use pyo3_ffi::*;
|
||||
|
||||
|
@ -72,13 +72,11 @@ pub unsafe extern "C" fn PyInit_string_sum() -> *mut PyObject {
|
|||
PyUnicode_FromStringAndSize(version.as_ptr() as *const c_char, version.len() as isize),
|
||||
);
|
||||
|
||||
// It's necessary to transmute `sum_as_string` here because functions marked with `METH_FASTCALL`
|
||||
// have a different signature. However the `PyMethodDef` struct currently represents all
|
||||
// functions as a `PyCFunction`. The python interpreter will cast the function pointer back
|
||||
// to `_PyCFunctionFast`.
|
||||
let wrapped_sum_as_string = PyMethodDef {
|
||||
ml_name: "sum_as_string\0".as_ptr() as *const c_char,
|
||||
ml_meth: Some(transmute::<_PyCFunctionFast, PyCFunction>(sum_as_string)),
|
||||
ml_meth: PyMethodDefPointer {
|
||||
_PyCFunctionFast: sum_as_string
|
||||
},
|
||||
ml_flags: METH_FASTCALL,
|
||||
ml_doc: "returns the sum of two integers as a string\0".as_ptr() as *const c_char,
|
||||
};
|
||||
|
@ -127,7 +125,7 @@ pub unsafe extern "C" fn sum_as_string(
|
|||
|
||||
let arg1 = PyLong_AsLong(arg1);
|
||||
if !PyErr_Occurred().is_null() {
|
||||
return ptr::null()
|
||||
return ptr::null_mut()
|
||||
}
|
||||
|
||||
let arg2 = *args.add(1);
|
||||
|
@ -137,7 +135,7 @@ pub unsafe extern "C" fn sum_as_string(
|
|||
|
||||
let arg2 = PyLong_AsLong(arg2);
|
||||
if !PyErr_Occurred().is_null() {
|
||||
return ptr::null()
|
||||
return ptr::null_mut()
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use pyo3_build_config::{
|
||||
bail, ensure,
|
||||
bail, ensure, print_feature_cfgs,
|
||||
pyo3_build_script_impl::{
|
||||
cargo_env_var, env_var, errors::Result, resolve_interpreter_config, InterpreterConfig,
|
||||
PythonVersion,
|
||||
|
@ -69,6 +69,9 @@ fn emit_link_config(interpreter_config: &InterpreterConfig) -> Result<()> {
|
|||
}
|
||||
}
|
||||
|
||||
// serialize the whole interpreter config in DEP_PYTHON_PYO3_CONFIG
|
||||
interpreter_config.to_cargo_dep_env()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -100,6 +103,9 @@ fn configure_pyo3() -> Result<()> {
|
|||
println!("{}", line);
|
||||
}
|
||||
|
||||
// Emit cfgs like `addr_of` and `min_const_generics`
|
||||
print_feature_cfgs();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ extern "C" {
|
|||
|
||||
#[inline]
|
||||
pub unsafe fn PyBool_Check(op: *mut PyObject) -> c_int {
|
||||
(Py_TYPE(op) == &mut PyBool_Type) as c_int
|
||||
(Py_TYPE(op) == addr_of_mut_shim!(PyBool_Type)) as c_int
|
||||
}
|
||||
|
||||
#[cfg_attr(windows, link(name = "pythonXY"))]
|
||||
|
@ -23,12 +23,12 @@ extern "C" {
|
|||
|
||||
#[inline]
|
||||
pub unsafe fn Py_False() -> *mut PyObject {
|
||||
&mut _Py_FalseStruct as *mut PyLongObject as *mut PyObject
|
||||
addr_of_mut_shim!(_Py_FalseStruct) as *mut PyLongObject as *mut PyObject
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub unsafe fn Py_True() -> *mut PyObject {
|
||||
&mut _Py_TrueStruct as *mut PyLongObject as *mut PyObject
|
||||
addr_of_mut_shim!(_Py_TrueStruct) as *mut PyLongObject as *mut PyObject
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
|
@ -12,12 +12,12 @@ extern "C" {
|
|||
|
||||
#[inline]
|
||||
pub unsafe fn PyByteArray_Check(op: *mut PyObject) -> c_int {
|
||||
PyObject_TypeCheck(op, &mut PyByteArray_Type)
|
||||
PyObject_TypeCheck(op, addr_of_mut_shim!(PyByteArray_Type))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub unsafe fn PyByteArray_CheckExact(op: *mut PyObject) -> c_int {
|
||||
(Py_TYPE(op) == &mut PyByteArray_Type) as c_int
|
||||
(Py_TYPE(op) == addr_of_mut_shim!(PyByteArray_Type)) as c_int
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
|
|
@ -16,7 +16,7 @@ pub unsafe fn PyBytes_Check(op: *mut PyObject) -> c_int {
|
|||
|
||||
#[inline]
|
||||
pub unsafe fn PyBytes_CheckExact(op: *mut PyObject) -> c_int {
|
||||
(Py_TYPE(op) == &mut PyBytes_Type) as c_int
|
||||
(Py_TYPE(op) == addr_of_mut_shim!(PyBytes_Type)) as c_int
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
|
|
@ -40,12 +40,12 @@ extern "C" {
|
|||
|
||||
#[inline]
|
||||
pub unsafe fn PyComplex_Check(op: *mut PyObject) -> c_int {
|
||||
PyObject_TypeCheck(op, &mut PyComplex_Type)
|
||||
PyObject_TypeCheck(op, addr_of_mut_shim!(PyComplex_Type))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub unsafe fn PyComplex_CheckExact(op: *mut PyObject) -> c_int {
|
||||
(Py_TYPE(op) == &mut PyComplex_Type) as c_int
|
||||
(Py_TYPE(op) == addr_of_mut_shim!(PyComplex_Type)) as c_int
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
|
|
@ -12,17 +12,17 @@ extern "C" {
|
|||
|
||||
#[inline]
|
||||
pub unsafe fn PyContext_CheckExact(op: *mut PyObject) -> c_int {
|
||||
(Py_TYPE(op) == &mut PyContext_Type) as c_int
|
||||
(Py_TYPE(op) == addr_of_mut_shim!(PyContext_Type)) as c_int
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub unsafe fn PyContextVar_CheckExact(op: *mut PyObject) -> c_int {
|
||||
(Py_TYPE(op) == &mut PyContextVar_Type) as c_int
|
||||
(Py_TYPE(op) == addr_of_mut_shim!(PyContextVar_Type)) as c_int
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub unsafe fn PyContextToken_CheckExact(op: *mut PyObject) -> c_int {
|
||||
(Py_TYPE(op) == &mut PyContextToken_Type) as c_int
|
||||
(Py_TYPE(op) == addr_of_mut_shim!(PyContextToken_Type)) as c_int
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
|
|
@ -9,7 +9,7 @@ use crate::{
|
|||
pyport::PY_SSIZE_T_MAX, vectorcallfunc, PyCallable_Check, PyThreadState, PyThreadState_GET,
|
||||
PyTuple_Check, PyType_HasFeature, Py_TPFLAGS_HAVE_VECTORCALL,
|
||||
};
|
||||
#[cfg(all(Py_3_8, not(PyPy)))]
|
||||
#[cfg(Py_3_8)]
|
||||
use libc::size_t;
|
||||
|
||||
extern "C" {
|
||||
|
@ -101,17 +101,29 @@ pub unsafe fn PyObject_Vectorcall(
|
|||
}
|
||||
|
||||
extern "C" {
|
||||
#[cfg(all(Py_3_8, not(PyPy)))]
|
||||
#[cfg_attr(not(Py_3_9), link_name = "_PyObject_VectorcallDict")]
|
||||
#[cfg(all(PyPy, Py_3_8))]
|
||||
#[cfg_attr(not(Py_3_9), link_name = "_PyPyObject_Vectorcall")]
|
||||
pub fn PyObject_Vectorcall(
|
||||
callable: *mut PyObject,
|
||||
args: *const *mut PyObject,
|
||||
nargsf: size_t,
|
||||
kwnames: *mut PyObject,
|
||||
) -> *mut PyObject;
|
||||
|
||||
#[cfg(all(Py_3_8))]
|
||||
#[cfg_attr(all(not(PyPy), not(Py_3_9)), link_name = "_PyObject_VectorcallDict")]
|
||||
#[cfg_attr(all(PyPy, not(Py_3_9)), link_name = "_PyPyObject_VectorcallDict")]
|
||||
#[cfg_attr(all(PyPy, Py_3_9), link_name = "PyPyObject_VectorcallDict")]
|
||||
pub fn PyObject_VectorcallDict(
|
||||
callable: *mut PyObject,
|
||||
args: *const *mut PyObject,
|
||||
nargsf: size_t,
|
||||
kwargs: *mut PyObject,
|
||||
kwdict: *mut PyObject,
|
||||
) -> *mut PyObject;
|
||||
|
||||
#[cfg(all(Py_3_8, not(PyPy)))]
|
||||
#[cfg_attr(not(Py_3_9), link_name = "_PyVectorcall_Call")]
|
||||
#[cfg(all(Py_3_8))]
|
||||
#[cfg_attr(not(any(Py_3_9, PyPy)), link_name = "_PyVectorcall_Call")]
|
||||
#[cfg_attr(PyPy, link_name = "PyPyVectorcall_Call")]
|
||||
pub fn PyVectorcall_Call(
|
||||
callable: *mut PyObject,
|
||||
tuple: *mut PyObject,
|
||||
|
@ -152,6 +164,12 @@ pub unsafe fn _PyObject_CallNoArg(func: *mut PyObject) -> *mut PyObject {
|
|||
)
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
#[cfg(PyPy)]
|
||||
#[link_name = "_PyPyObject_CallNoArg"]
|
||||
pub fn _PyObject_CallNoArg(func: *mut PyObject) -> *mut PyObject;
|
||||
}
|
||||
|
||||
#[cfg(all(Py_3_8, not(PyPy)))]
|
||||
#[inline(always)]
|
||||
pub unsafe fn PyObject_CallOneArg(func: *mut PyObject, arg: *mut PyObject) -> *mut PyObject {
|
||||
|
|
|
@ -84,7 +84,7 @@ extern "C" {
|
|||
#[inline]
|
||||
#[cfg(not(PyPy))]
|
||||
pub unsafe fn PyCode_Check(op: *mut PyObject) -> c_int {
|
||||
(Py_TYPE(op) == &mut PyCode_Type) as c_int
|
||||
(Py_TYPE(op) == addr_of_mut_shim!(PyCode_Type)) as c_int
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
|
@ -60,7 +60,7 @@ extern "C" {
|
|||
|
||||
#[inline]
|
||||
pub unsafe fn PyFrame_Check(op: *mut PyObject) -> c_int {
|
||||
(Py_TYPE(op) == &mut PyFrame_Type) as c_int
|
||||
(Py_TYPE(op) == addr_of_mut_shim!(PyFrame_Type)) as c_int
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
|
|
@ -359,7 +359,6 @@ pub struct PyDateTime_CAPI {
|
|||
pub TimeType: *mut PyTypeObject,
|
||||
pub DeltaType: *mut PyTypeObject,
|
||||
pub TZInfoType: *mut PyTypeObject,
|
||||
#[cfg(not(all(PyPy, not(Py_3_8))))]
|
||||
pub TimeZone_UTC: *mut PyObject,
|
||||
pub Date_FromDate: unsafe extern "C" fn(
|
||||
year: c_int,
|
||||
|
@ -393,7 +392,6 @@ pub struct PyDateTime_CAPI {
|
|||
normalize: c_int,
|
||||
cls: *mut PyTypeObject,
|
||||
) -> *mut PyObject,
|
||||
#[cfg(not(all(PyPy, not(Py_3_8))))]
|
||||
pub TimeZone_FromTimeZone:
|
||||
unsafe extern "C" fn(offset: *mut PyObject, name: *mut PyObject) -> *mut PyObject,
|
||||
|
||||
|
@ -442,7 +440,6 @@ pub unsafe fn PyDateTimeAPI() -> *mut PyDateTime_CAPI {
|
|||
*PyDateTimeAPI_impl.0.get()
|
||||
}
|
||||
|
||||
#[cfg(not(all(PyPy, not(Py_3_8))))]
|
||||
#[inline]
|
||||
pub unsafe fn PyDateTime_TimeZone_UTC() -> *mut PyObject {
|
||||
(*PyDateTimeAPI()).TimeZone_UTC
|
||||
|
|
|
@ -15,7 +15,7 @@ pub unsafe fn PyDict_Check(op: *mut PyObject) -> c_int {
|
|||
|
||||
#[inline]
|
||||
pub unsafe fn PyDict_CheckExact(op: *mut PyObject) -> c_int {
|
||||
(Py_TYPE(op) == &mut PyDict_Type) as c_int
|
||||
(Py_TYPE(op) == addr_of_mut_shim!(PyDict_Type)) as c_int
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
@ -76,17 +76,17 @@ extern "C" {
|
|||
|
||||
#[inline]
|
||||
pub unsafe fn PyDictKeys_Check(op: *mut PyObject) -> c_int {
|
||||
(Py_TYPE(op) == &mut PyDictKeys_Type) as c_int
|
||||
(Py_TYPE(op) == addr_of_mut_shim!(PyDictKeys_Type)) as c_int
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub unsafe fn PyDictValues_Check(op: *mut PyObject) -> c_int {
|
||||
(Py_TYPE(op) == &mut PyDictValues_Type) as c_int
|
||||
(Py_TYPE(op) == addr_of_mut_shim!(PyDictValues_Type)) as c_int
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub unsafe fn PyDictItems_Check(op: *mut PyObject) -> c_int {
|
||||
(Py_TYPE(op) == &mut PyDictItems_Type) as c_int
|
||||
(Py_TYPE(op) == addr_of_mut_shim!(PyDictItems_Type)) as c_int
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
|
@ -20,12 +20,12 @@ extern "C" {
|
|||
|
||||
#[inline]
|
||||
pub unsafe fn PyFloat_Check(op: *mut PyObject) -> c_int {
|
||||
PyObject_TypeCheck(op, &mut PyFloat_Type)
|
||||
PyObject_TypeCheck(op, addr_of_mut_shim!(PyFloat_Type))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub unsafe fn PyFloat_CheckExact(op: *mut PyObject) -> c_int {
|
||||
(Py_TYPE(op) == &mut PyFloat_Type) as c_int
|
||||
(Py_TYPE(op) == addr_of_mut_shim!(PyFloat_Type)) as c_int
|
||||
}
|
||||
|
||||
// skipped Py_RETURN_NAN
|
||||
|
|
|
@ -12,7 +12,7 @@ extern "C" {
|
|||
|
||||
#[inline]
|
||||
pub unsafe fn PyFunction_Check(op: *mut PyObject) -> c_int {
|
||||
(Py_TYPE(op) == &mut PyFunction_Type) as c_int
|
||||
(Py_TYPE(op) == addr_of_mut_shim!(PyFunction_Type)) as c_int
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
|
|
@ -25,12 +25,12 @@ extern "C" {
|
|||
|
||||
#[inline]
|
||||
pub unsafe fn PyGen_Check(op: *mut PyObject) -> c_int {
|
||||
PyObject_TypeCheck(op, &mut PyGen_Type)
|
||||
PyObject_TypeCheck(op, addr_of_mut_shim!(PyGen_Type))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub unsafe fn PyGen_CheckExact(op: *mut PyObject) -> c_int {
|
||||
(Py_TYPE(op) == &mut PyGen_Type) as c_int
|
||||
(Py_TYPE(op) == addr_of_mut_shim!(PyGen_Type)) as c_int
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
@ -55,7 +55,7 @@ extern "C" {
|
|||
|
||||
#[inline]
|
||||
pub unsafe fn PyCoro_CheckExact(op: *mut PyObject) -> c_int {
|
||||
PyObject_TypeCheck(op, &mut PyCoro_Type)
|
||||
PyObject_TypeCheck(op, addr_of_mut_shim!(PyCoro_Type))
|
||||
}
|
||||
|
||||
// skipped _PyCoro_GetAwaitableIter
|
||||
|
@ -75,7 +75,7 @@ extern "C" {
|
|||
|
||||
#[inline]
|
||||
pub unsafe fn PyAsyncGen_CheckExact(op: *mut PyObject) -> c_int {
|
||||
PyObject_TypeCheck(op, &mut PyAsyncGen_Type)
|
||||
PyObject_TypeCheck(op, addr_of_mut_shim!(PyAsyncGen_Type))
|
||||
}
|
||||
|
||||
// skipped _PyAsyncGenValueWrapperNew
|
||||
|
|
|
@ -9,7 +9,7 @@ extern "C" {
|
|||
|
||||
#[inline]
|
||||
pub unsafe fn PySeqIter_Check(op: *mut PyObject) -> c_int {
|
||||
(Py_TYPE(op) == &mut PySeqIter_Type) as c_int
|
||||
(Py_TYPE(op) == addr_of_mut_shim!(PySeqIter_Type)) as c_int
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
@ -19,7 +19,7 @@ extern "C" {
|
|||
|
||||
#[inline]
|
||||
pub unsafe fn PyCallIter_Check(op: *mut PyObject) -> c_int {
|
||||
(Py_TYPE(op) == &mut PyCallIter_Type) as c_int
|
||||
(Py_TYPE(op) == addr_of_mut_shim!(PyCallIter_Type)) as c_int
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
|
|
@ -83,8 +83,8 @@
|
|||
//!
|
||||
//! **`src/lib.rs`**
|
||||
//! ```rust
|
||||
//! use std::mem::transmute;
|
||||
//! use std::os::raw::c_char;
|
||||
//! use std::ptr;
|
||||
//!
|
||||
//! use pyo3_ffi::*;
|
||||
//!
|
||||
|
@ -111,13 +111,11 @@
|
|||
//! PyUnicode_FromStringAndSize(version.as_ptr() as *const c_char, version.len() as isize),
|
||||
//! );
|
||||
//!
|
||||
//! // It's necessary to transmute `sum_as_string` here because functions marked with `METH_FASTCALL`
|
||||
//! // have a different signature. However the `PyMethodDef` struct currently represents all
|
||||
//! // functions as a `PyCFunction`. The python interpreter will cast the function pointer back
|
||||
//! // to `_PyCFunctionFast`.
|
||||
//! let wrapped_sum_as_string = PyMethodDef {
|
||||
//! ml_name: "sum_as_string\0".as_ptr() as *const c_char,
|
||||
//! ml_meth: Some(transmute::<_PyCFunctionFast, PyCFunction>(sum_as_string)),
|
||||
//! ml_meth: PyMethodDefPointer {
|
||||
//! _PyCFunctionFast: sum_as_string
|
||||
//! },
|
||||
//! ml_flags: METH_FASTCALL,
|
||||
//! ml_doc: "returns the sum of two integers as a string\0".as_ptr() as *const c_char,
|
||||
//! };
|
||||
|
@ -166,7 +164,7 @@
|
|||
//!
|
||||
//! let arg1 = PyLong_AsLong(arg1);
|
||||
//! if !PyErr_Occurred().is_null() {
|
||||
//! return ptr::null()
|
||||
//! return ptr::null_mut()
|
||||
//! }
|
||||
//!
|
||||
//! let arg2 = *args.add(1);
|
||||
|
@ -176,7 +174,7 @@
|
|||
//!
|
||||
//! let arg2 = PyLong_AsLong(arg2);
|
||||
//! if !PyErr_Occurred().is_null() {
|
||||
//! return ptr::null()
|
||||
//! return ptr::null_mut()
|
||||
//! }
|
||||
//! let res = (arg1 + arg2).to_string();
|
||||
//! PyUnicode_FromStringAndSize(res.as_ptr() as *const c_char, res.len() as isize)
|
||||
|
@ -254,6 +252,7 @@
|
|||
clippy::upper_case_acronyms,
|
||||
clippy::missing_safety_doc
|
||||
)]
|
||||
#![warn(elided_lifetimes_in_paths, unused_lifetimes)]
|
||||
|
||||
// Until `extern type` is stabilized, use the recommended approach to
|
||||
// model opaque types:
|
||||
|
@ -265,6 +264,19 @@ macro_rules! opaque_struct {
|
|||
};
|
||||
}
|
||||
|
||||
macro_rules! addr_of_mut_shim {
|
||||
($place:expr) => {{
|
||||
#[cfg(addr_of)]
|
||||
{
|
||||
::std::ptr::addr_of_mut!($place)
|
||||
}
|
||||
#[cfg(not(addr_of))]
|
||||
{
|
||||
&mut $place as *mut _
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
pub use self::abstract_::*;
|
||||
pub use self::bltinmodule::*;
|
||||
pub use self::boolobject::*;
|
||||
|
|
|
@ -17,7 +17,7 @@ pub unsafe fn PyList_Check(op: *mut PyObject) -> c_int {
|
|||
|
||||
#[inline]
|
||||
pub unsafe fn PyList_CheckExact(op: *mut PyObject) -> c_int {
|
||||
(Py_TYPE(op) == &mut PyList_Type) as c_int
|
||||
(Py_TYPE(op) == addr_of_mut_shim!(PyList_Type)) as c_int
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
|
|
@ -20,7 +20,7 @@ pub unsafe fn PyLong_Check(op: *mut PyObject) -> c_int {
|
|||
|
||||
#[inline]
|
||||
pub unsafe fn PyLong_CheckExact(op: *mut PyObject) -> c_int {
|
||||
(Py_TYPE(op) == &mut PyLong_Type) as c_int
|
||||
(Py_TYPE(op) == addr_of_mut_shim!(PyLong_Type)) as c_int
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
@ -97,7 +97,7 @@ extern "C" {
|
|||
// skipped non-limited _PyLong_Sign
|
||||
|
||||
#[cfg_attr(PyPy, link_name = "_PyPyLong_NumBits")]
|
||||
pub fn _PyLong_NumBits(obj: *mut PyObject) -> c_int;
|
||||
pub fn _PyLong_NumBits(obj: *mut PyObject) -> size_t;
|
||||
|
||||
// skipped _PyLong_DivmodNear
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ extern "C" {
|
|||
|
||||
#[inline]
|
||||
pub unsafe fn PyMemoryView_Check(op: *mut PyObject) -> c_int {
|
||||
(Py_TYPE(op) == &mut PyMemoryView_Type) as c_int
|
||||
(Py_TYPE(op) == addr_of_mut_shim!(PyMemoryView_Type)) as c_int
|
||||
}
|
||||
|
||||
// skipped non-limited PyMemoryView_GET_BUFFER
|
||||
|
|
|
@ -13,25 +13,25 @@ extern "C" {
|
|||
#[cfg(Py_3_9)]
|
||||
#[inline]
|
||||
pub unsafe fn PyCFunction_CheckExact(op: *mut PyObject) -> c_int {
|
||||
(Py_TYPE(op) == &mut PyCFunction_Type) as c_int
|
||||
(Py_TYPE(op) == addr_of_mut_shim!(PyCFunction_Type)) as c_int
|
||||
}
|
||||
|
||||
#[cfg(Py_3_9)]
|
||||
#[inline]
|
||||
pub unsafe fn PyCFunction_Check(op: *mut PyObject) -> c_int {
|
||||
PyObject_TypeCheck(op, &mut PyCFunction_Type)
|
||||
PyObject_TypeCheck(op, addr_of_mut_shim!(PyCFunction_Type))
|
||||
}
|
||||
|
||||
#[cfg(not(Py_3_9))]
|
||||
#[inline]
|
||||
pub unsafe fn PyCFunction_Check(op: *mut PyObject) -> c_int {
|
||||
(Py_TYPE(op) == &mut PyCFunction_Type) as c_int
|
||||
(Py_TYPE(op) == addr_of_mut_shim!(PyCFunction_Type)) as c_int
|
||||
}
|
||||
|
||||
pub type PyCFunction =
|
||||
unsafe extern "C" fn(slf: *mut PyObject, args: *mut PyObject) -> *mut PyObject;
|
||||
|
||||
#[cfg(not(Py_LIMITED_API))]
|
||||
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
|
||||
pub type _PyCFunctionFast = unsafe extern "C" fn(
|
||||
slf: *mut PyObject,
|
||||
args: *mut *mut PyObject,
|
||||
|
@ -52,7 +52,14 @@ pub type _PyCFunctionFastWithKeywords = unsafe extern "C" fn(
|
|||
kwnames: *mut PyObject,
|
||||
) -> *mut PyObject;
|
||||
|
||||
// skipped PyCMethod (since 3.9)
|
||||
#[cfg(all(Py_3_9, not(Py_LIMITED_API)))]
|
||||
pub type PyCMethod = unsafe extern "C" fn(
|
||||
slf: *mut PyObject,
|
||||
defining_class: *mut PyTypeObject,
|
||||
args: *const *mut PyObject,
|
||||
nargs: crate::pyport::Py_ssize_t,
|
||||
kwnames: *mut PyObject,
|
||||
) -> *mut PyObject;
|
||||
|
||||
extern "C" {
|
||||
#[cfg_attr(PyPy, link_name = "PyPyCFunction_GetFunction")]
|
||||
|
@ -71,17 +78,46 @@ extern "C" {
|
|||
#[derive(Copy, Clone)]
|
||||
pub struct PyMethodDef {
|
||||
pub ml_name: *const c_char,
|
||||
pub ml_meth: Option<PyCFunction>,
|
||||
pub ml_meth: PyMethodDefPointer,
|
||||
pub ml_flags: c_int,
|
||||
pub ml_doc: *const c_char,
|
||||
}
|
||||
|
||||
impl Default for PyMethodDef {
|
||||
fn default() -> PyMethodDef {
|
||||
unsafe { mem::zeroed() }
|
||||
}
|
||||
/// Function types used to implement Python callables.
|
||||
///
|
||||
/// This function pointer must be accompanied by the correct [ml_flags](PyMethodDef::ml_flags),
|
||||
/// otherwise the behavior is undefined.
|
||||
///
|
||||
/// See the [Python C API documentation][1] for more information.
|
||||
///
|
||||
/// [1]: https://docs.python.org/3/c-api/structures.html#implementing-functions-and-methods
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub union PyMethodDefPointer {
|
||||
/// This variant corresponds with [`METH_VARARGS`] *or* [`METH_NOARGS`] *or* [`METH_O`].
|
||||
pub PyCFunction: PyCFunction,
|
||||
|
||||
/// This variant corresponds with [`METH_VARARGS`] | [`METH_KEYWORDS`].
|
||||
pub PyCFunctionWithKeywords: PyCFunctionWithKeywords,
|
||||
|
||||
/// This variant corresponds with [`METH_FASTCALL`].
|
||||
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
|
||||
pub _PyCFunctionFast: _PyCFunctionFast,
|
||||
|
||||
/// This variant corresponds with [`METH_FASTCALL`] | [`METH_KEYWORDS`].
|
||||
#[cfg(not(Py_LIMITED_API))]
|
||||
pub _PyCFunctionFastWithKeywords: _PyCFunctionFastWithKeywords,
|
||||
|
||||
/// This variant corresponds with [`METH_METHOD`] | [`METH_FASTCALL`] | [`METH_KEYWORDS`].
|
||||
#[cfg(all(Py_3_9, not(Py_LIMITED_API)))]
|
||||
pub PyCMethod: PyCMethod,
|
||||
}
|
||||
|
||||
// TODO: This can be a const assert on Rust 1.57
|
||||
const _: () =
|
||||
[()][mem::size_of::<PyMethodDefPointer>() - mem::size_of::<Option<extern "C" fn()>>()];
|
||||
|
||||
#[cfg(not(Py_3_9))]
|
||||
extern "C" {
|
||||
#[cfg_attr(PyPy, link_name = "PyPyCFunction_New")]
|
||||
pub fn PyCFunction_New(ml: *mut PyMethodDef, slf: *mut PyObject) -> *mut PyObject;
|
||||
|
@ -94,7 +130,32 @@ extern "C" {
|
|||
) -> *mut PyObject;
|
||||
}
|
||||
|
||||
// skipped non-limited / 3.9 PyCMethod_New
|
||||
#[cfg(Py_3_9)]
|
||||
#[inline]
|
||||
pub unsafe fn PyCFunction_New(ml: *mut PyMethodDef, slf: *mut PyObject) -> *mut PyObject {
|
||||
PyCFunction_NewEx(ml, slf, std::ptr::null_mut())
|
||||
}
|
||||
|
||||
#[cfg(Py_3_9)]
|
||||
#[inline]
|
||||
pub unsafe fn PyCFunction_NewEx(
|
||||
ml: *mut PyMethodDef,
|
||||
slf: *mut PyObject,
|
||||
module: *mut PyObject,
|
||||
) -> *mut PyObject {
|
||||
PyCMethod_New(ml, slf, module, std::ptr::null_mut())
|
||||
}
|
||||
|
||||
#[cfg(Py_3_9)]
|
||||
extern "C" {
|
||||
#[cfg_attr(PyPy, link_name = "PyPyCMethod_New")]
|
||||
pub fn PyCMethod_New(
|
||||
ml: *mut PyMethodDef,
|
||||
slf: *mut PyObject,
|
||||
module: *mut PyObject,
|
||||
cls: *mut PyTypeObject,
|
||||
) -> *mut PyObject;
|
||||
}
|
||||
|
||||
/* Flag passed to newmethodobject */
|
||||
pub const METH_VARARGS: c_int = 0x0001;
|
||||
|
@ -118,11 +179,13 @@ pub const METH_COEXIST: c_int = 0x0040;
|
|||
|
||||
/* METH_FASTCALL indicates the PEP 590 Vectorcall calling format. It may
|
||||
be specified alone or with METH_KEYWORDS. */
|
||||
#[cfg(not(Py_LIMITED_API))]
|
||||
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
|
||||
pub const METH_FASTCALL: c_int = 0x0080;
|
||||
|
||||
// skipped METH_STACKLESS
|
||||
// skipped METH_METHOD
|
||||
|
||||
#[cfg(all(Py_3_9, not(Py_LIMITED_API)))]
|
||||
pub const METH_METHOD: c_int = 0x0200;
|
||||
|
||||
extern "C" {
|
||||
#[cfg(not(Py_3_9))]
|
||||
|
|
|
@ -77,7 +77,12 @@ extern "C" {
|
|||
name: *const c_char,
|
||||
value: *const c_char,
|
||||
) -> c_int;
|
||||
// skipped non-limited / 3.9 PyModule_AddType
|
||||
#[cfg(any(Py_3_10, all(Py_3_9, not(Py_LIMITED_API))))]
|
||||
#[cfg_attr(PyPy, link_name = "PyPyModule_AddType")]
|
||||
pub fn PyModule_AddType(
|
||||
module: *mut PyObject,
|
||||
type_: *mut crate::object::PyTypeObject,
|
||||
) -> c_int;
|
||||
// skipped PyModule_AddIntMacro
|
||||
// skipped PyModule_AddStringMacro
|
||||
pub fn PyModule_SetDocString(arg1: *mut PyObject, arg2: *const c_char) -> c_int;
|
||||
|
|
|
@ -11,12 +11,12 @@ extern "C" {
|
|||
|
||||
#[inline]
|
||||
pub unsafe fn PyModule_Check(op: *mut PyObject) -> c_int {
|
||||
PyObject_TypeCheck(op, &mut PyModule_Type)
|
||||
PyObject_TypeCheck(op, addr_of_mut_shim!(PyModule_Type))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub unsafe fn PyModule_CheckExact(op: *mut PyObject) -> c_int {
|
||||
(Py_TYPE(op) == &mut PyModule_Type) as c_int
|
||||
(Py_TYPE(op) == addr_of_mut_shim!(PyModule_Type)) as c_int
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
|
|
@ -63,9 +63,7 @@ pub unsafe fn Py_Is(x: *mut PyObject, y: *mut PyObject) -> c_int {
|
|||
|
||||
#[inline]
|
||||
pub unsafe fn Py_REFCNT(ob: *mut PyObject) -> Py_ssize_t {
|
||||
if ob.is_null() {
|
||||
panic!();
|
||||
}
|
||||
assert!(!ob.is_null());
|
||||
(*ob).ob_refcnt
|
||||
}
|
||||
|
||||
|
@ -207,9 +205,21 @@ extern "C" {
|
|||
#[cfg_attr(PyPy, link_name = "PyPyType_GetSlot")]
|
||||
pub fn PyType_GetSlot(arg1: *mut PyTypeObject, arg2: c_int) -> *mut c_void;
|
||||
|
||||
// skipped non-limited / 3.9 PyType_FromModuleAndSpec
|
||||
// skipped non-limited / 3.9 PyType_GetModule
|
||||
// skipped non-limited / 3.9 PyType_GetModuleState
|
||||
#[cfg(any(Py_3_10, all(Py_3_9, not(Py_LIMITED_API))))]
|
||||
#[cfg_attr(PyPy, link_name = "PyPyType_FromModuleAndSpec")]
|
||||
pub fn PyType_FromModuleAndSpec(
|
||||
module: *mut PyObject,
|
||||
spec: *mut PyType_Spec,
|
||||
bases: *mut PyObject,
|
||||
) -> *mut PyObject;
|
||||
|
||||
#[cfg(any(Py_3_10, all(Py_3_9, not(Py_LIMITED_API))))]
|
||||
#[cfg_attr(PyPy, link_name = "PyPyType_GetModule")]
|
||||
pub fn PyType_GetModule(arg1: *mut PyTypeObject) -> *mut PyObject;
|
||||
|
||||
#[cfg(any(Py_3_10, all(Py_3_9, not(Py_LIMITED_API))))]
|
||||
#[cfg_attr(PyPy, link_name = "PyPyType_GetModuleState")]
|
||||
pub fn PyType_GetModuleState(arg1: *mut PyTypeObject) -> *mut c_void;
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
@ -412,7 +422,7 @@ pub unsafe fn Py_DECREF(op: *mut PyObject) {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub unsafe fn Py_CLEAR(op: &mut *mut PyObject) {
|
||||
pub unsafe fn Py_CLEAR(op: *mut *mut PyObject) {
|
||||
let tmp = *op;
|
||||
if !tmp.is_null() {
|
||||
*op = ptr::null_mut();
|
||||
|
@ -470,7 +480,7 @@ extern "C" {
|
|||
|
||||
#[inline]
|
||||
pub unsafe fn Py_None() -> *mut PyObject {
|
||||
&mut _Py_NoneStruct
|
||||
addr_of_mut_shim!(_Py_NoneStruct)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -488,7 +498,7 @@ extern "C" {
|
|||
|
||||
#[inline]
|
||||
pub unsafe fn Py_NotImplemented() -> *mut PyObject {
|
||||
&mut _Py_NotImplementedStruct
|
||||
addr_of_mut_shim!(_Py_NotImplementedStruct)
|
||||
}
|
||||
|
||||
// skipped Py_RETURN_NOTIMPLEMENTED
|
||||
|
@ -536,5 +546,5 @@ pub unsafe fn PyType_Check(op: *mut PyObject) -> c_int {
|
|||
|
||||
#[inline]
|
||||
pub unsafe fn PyType_CheckExact(op: *mut PyObject) -> c_int {
|
||||
(Py_TYPE(op) == &mut PyType_Type) as c_int
|
||||
(Py_TYPE(op) == addr_of_mut_shim!(PyType_Type)) as c_int
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ pub type PyCapsule_Destructor = unsafe extern "C" fn(o: *mut PyObject);
|
|||
|
||||
#[inline]
|
||||
pub unsafe fn PyCapsule_CheckExact(ob: *mut PyObject) -> c_int {
|
||||
(Py_TYPE(ob) == &mut PyCapsule_Type) as c_int
|
||||
(Py_TYPE(ob) == addr_of_mut_shim!(PyCapsule_Type)) as c_int
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
|
|
@ -11,5 +11,5 @@ extern "C" {
|
|||
|
||||
#[inline]
|
||||
pub unsafe fn PyRange_Check(op: *mut PyObject) -> c_int {
|
||||
(Py_TYPE(op) == &mut PyRange_Type) as c_int
|
||||
(Py_TYPE(op) == addr_of_mut_shim!(PyRange_Type)) as c_int
|
||||
}
|
||||
|
|
|
@ -82,7 +82,7 @@ extern "C" {
|
|||
#[inline]
|
||||
#[cfg(not(PyPy))]
|
||||
pub unsafe fn PyFrozenSet_CheckExact(ob: *mut PyObject) -> c_int {
|
||||
(Py_TYPE(ob) == &mut PyFrozenSet_Type) as c_int
|
||||
(Py_TYPE(ob) == addr_of_mut_shim!(PyFrozenSet_Type)) as c_int
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
@ -94,8 +94,8 @@ extern "C" {
|
|||
#[inline]
|
||||
#[cfg(not(PyPy))]
|
||||
pub unsafe fn PyFrozenSet_Check(ob: *mut PyObject) -> c_int {
|
||||
(Py_TYPE(ob) == &mut PyFrozenSet_Type
|
||||
|| PyType_IsSubtype(Py_TYPE(ob), &mut PyFrozenSet_Type) != 0) as c_int
|
||||
(Py_TYPE(ob) == addr_of_mut_shim!(PyFrozenSet_Type)
|
||||
|| PyType_IsSubtype(Py_TYPE(ob), addr_of_mut_shim!(PyFrozenSet_Type)) != 0) as c_int
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
@ -107,20 +107,21 @@ extern "C" {
|
|||
#[inline]
|
||||
#[cfg(not(PyPy))]
|
||||
pub unsafe fn PyAnySet_CheckExact(ob: *mut PyObject) -> c_int {
|
||||
(Py_TYPE(ob) == &mut PySet_Type || Py_TYPE(ob) == &mut PyFrozenSet_Type) as c_int
|
||||
(Py_TYPE(ob) == addr_of_mut_shim!(PySet_Type)
|
||||
|| Py_TYPE(ob) == addr_of_mut_shim!(PyFrozenSet_Type)) as c_int
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub unsafe fn PyAnySet_Check(ob: *mut PyObject) -> c_int {
|
||||
(PyAnySet_CheckExact(ob) != 0
|
||||
|| PyType_IsSubtype(Py_TYPE(ob), &mut PySet_Type) != 0
|
||||
|| PyType_IsSubtype(Py_TYPE(ob), &mut PyFrozenSet_Type) != 0) as c_int
|
||||
|| PyType_IsSubtype(Py_TYPE(ob), addr_of_mut_shim!(PySet_Type)) != 0
|
||||
|| PyType_IsSubtype(Py_TYPE(ob), addr_of_mut_shim!(PyFrozenSet_Type)) != 0) as c_int
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(Py_3_10)]
|
||||
pub unsafe fn PySet_CheckExact(op: *mut PyObject) -> c_int {
|
||||
crate::Py_IS_TYPE(op, &mut PySet_Type)
|
||||
crate::Py_IS_TYPE(op, addr_of_mut_shim!(PySet_Type))
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
@ -132,5 +133,6 @@ extern "C" {
|
|||
#[inline]
|
||||
#[cfg(not(PyPy))]
|
||||
pub unsafe fn PySet_Check(ob: *mut PyObject) -> c_int {
|
||||
(Py_TYPE(ob) == &mut PySet_Type || PyType_IsSubtype(Py_TYPE(ob), &mut PySet_Type) != 0) as c_int
|
||||
(Py_TYPE(ob) == addr_of_mut_shim!(PySet_Type)
|
||||
|| PyType_IsSubtype(Py_TYPE(ob), addr_of_mut_shim!(PySet_Type)) != 0) as c_int
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ extern "C" {
|
|||
|
||||
#[inline]
|
||||
pub unsafe fn Py_Ellipsis() -> *mut PyObject {
|
||||
&mut _Py_EllipsisObject
|
||||
addr_of_mut_shim!(_Py_EllipsisObject)
|
||||
}
|
||||
|
||||
#[cfg(not(Py_LIMITED_API))]
|
||||
|
@ -30,7 +30,7 @@ extern "C" {
|
|||
|
||||
#[inline]
|
||||
pub unsafe fn PySlice_Check(op: *mut PyObject) -> c_int {
|
||||
(Py_TYPE(op) == &mut PySlice_Type) as c_int
|
||||
(Py_TYPE(op) == addr_of_mut_shim!(PySlice_Type)) as c_int
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
|
|
@ -21,5 +21,5 @@ extern "C" {
|
|||
#[inline]
|
||||
#[cfg(not(PyPy))]
|
||||
pub unsafe fn PyTraceBack_Check(op: *mut PyObject) -> c_int {
|
||||
(Py_TYPE(op) == &mut PyTraceBack_Type) as c_int
|
||||
(Py_TYPE(op) == addr_of_mut_shim!(PyTraceBack_Type)) as c_int
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ pub unsafe fn PyTuple_Check(op: *mut PyObject) -> c_int {
|
|||
|
||||
#[inline]
|
||||
pub unsafe fn PyTuple_CheckExact(op: *mut PyObject) -> c_int {
|
||||
(Py_TYPE(op) == &mut PyTuple_Type) as c_int
|
||||
(Py_TYPE(op) == addr_of_mut_shim!(PyTuple_Type)) as c_int
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
|
|
@ -34,7 +34,7 @@ pub unsafe fn PyUnicode_Check(op: *mut PyObject) -> c_int {
|
|||
#[inline]
|
||||
#[cfg(not(PyPy))]
|
||||
pub unsafe fn PyUnicode_CheckExact(op: *mut PyObject) -> c_int {
|
||||
(Py_TYPE(op) == &mut PyUnicode_Type) as c_int
|
||||
(Py_TYPE(op) == addr_of_mut_shim!(PyUnicode_Type)) as c_int
|
||||
}
|
||||
|
||||
pub const Py_UNICODE_REPLACEMENT_CHARACTER: Py_UCS4 = 0xFFFD;
|
||||
|
|
|
@ -24,20 +24,20 @@ extern "C" {
|
|||
#[inline]
|
||||
#[cfg(not(PyPy))]
|
||||
pub unsafe fn PyWeakref_CheckRef(op: *mut PyObject) -> c_int {
|
||||
PyObject_TypeCheck(op, &mut _PyWeakref_RefType)
|
||||
PyObject_TypeCheck(op, addr_of_mut_shim!(_PyWeakref_RefType))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(PyPy))]
|
||||
pub unsafe fn PyWeakref_CheckRefExact(op: *mut PyObject) -> c_int {
|
||||
(Py_TYPE(op) == &mut _PyWeakref_RefType) as c_int
|
||||
(Py_TYPE(op) == addr_of_mut_shim!(_PyWeakref_RefType)) as c_int
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(PyPy))]
|
||||
pub unsafe fn PyWeakref_CheckProxy(op: *mut PyObject) -> c_int {
|
||||
((Py_TYPE(op) == &mut _PyWeakref_ProxyType)
|
||||
|| (Py_TYPE(op) == &mut _PyWeakref_CallableProxyType)) as c_int
|
||||
((Py_TYPE(op) == addr_of_mut_shim!(_PyWeakref_ProxyType))
|
||||
|| (Py_TYPE(op) == addr_of_mut_shim!(_PyWeakref_CallableProxyType))) as c_int
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "pyo3-macros-backend"
|
||||
version = "0.15.1"
|
||||
version = "0.16.2"
|
||||
description = "Code generation for PyO3 package"
|
||||
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
|
||||
keywords = ["pyo3", "python", "cpython", "ffi"]
|
||||
|
@ -16,12 +16,12 @@ edition = "2018"
|
|||
[dependencies]
|
||||
quote = { version = "1", default-features = false }
|
||||
proc-macro2 = { version = "1", default-features = false }
|
||||
pyo3-build-config = { path = "../pyo3-build-config", version = "0.15.1", features = ["resolve-config"] }
|
||||
|
||||
[dependencies.syn]
|
||||
version = "1"
|
||||
version = "1.0.56"
|
||||
default-features = false
|
||||
features = ["derive", "parsing", "printing", "clone-impls", "full", "extra-traits"]
|
||||
|
||||
[features]
|
||||
pyproto = []
|
||||
abi3 = []
|
||||
|
|
|
@ -1,77 +1,107 @@
|
|||
use proc_macro2::TokenStream;
|
||||
use quote::ToTokens;
|
||||
use syn::{
|
||||
parse::{Parse, ParseStream},
|
||||
punctuated::Punctuated,
|
||||
spanned::Spanned,
|
||||
token::Comma,
|
||||
Attribute, ExprPath, Ident, LitStr, Path, Result, Token,
|
||||
Attribute, Expr, ExprPath, Ident, LitStr, Path, Result, Token,
|
||||
};
|
||||
|
||||
pub mod kw {
|
||||
syn::custom_keyword!(annotation);
|
||||
syn::custom_keyword!(attribute);
|
||||
syn::custom_keyword!(dict);
|
||||
syn::custom_keyword!(extends);
|
||||
syn::custom_keyword!(freelist);
|
||||
syn::custom_keyword!(from_py_with);
|
||||
syn::custom_keyword!(gc);
|
||||
syn::custom_keyword!(get);
|
||||
syn::custom_keyword!(item);
|
||||
syn::custom_keyword!(pass_module);
|
||||
syn::custom_keyword!(module);
|
||||
syn::custom_keyword!(name);
|
||||
syn::custom_keyword!(pass_module);
|
||||
syn::custom_keyword!(set);
|
||||
syn::custom_keyword!(signature);
|
||||
syn::custom_keyword!(subclass);
|
||||
syn::custom_keyword!(text_signature);
|
||||
syn::custom_keyword!(transparent);
|
||||
syn::custom_keyword!(unsendable);
|
||||
syn::custom_keyword!(weakref);
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct FromPyWithAttribute(pub ExprPath);
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct KeywordAttribute<K, V> {
|
||||
pub kw: K,
|
||||
pub value: V,
|
||||
}
|
||||
|
||||
impl Parse for FromPyWithAttribute {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let _: kw::from_py_with = input.parse()?;
|
||||
let _: Token![=] = input.parse()?;
|
||||
let string_literal: LitStr = input.parse()?;
|
||||
string_literal.parse().map(FromPyWithAttribute)
|
||||
/// A helper type which parses the inner type via a literal string
|
||||
/// e.g. LitStrValue<Path> -> parses "some::path" in quotes.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct LitStrValue<T>(pub T);
|
||||
|
||||
impl<T: Parse> Parse for LitStrValue<T> {
|
||||
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
||||
let lit_str: LitStr = input.parse()?;
|
||||
lit_str.parse().map(LitStrValue)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct NameAttribute(pub Ident);
|
||||
|
||||
impl Parse for NameAttribute {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let _: kw::name = input.parse()?;
|
||||
let _: Token![=] = input.parse()?;
|
||||
let string_literal: LitStr = input.parse()?;
|
||||
string_literal.parse().map(NameAttribute)
|
||||
impl<T: ToTokens> ToTokens for LitStrValue<T> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
self.0.to_tokens(tokens)
|
||||
}
|
||||
}
|
||||
|
||||
/// A helper type which parses a name via a literal string
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct NameLitStr(pub Ident);
|
||||
|
||||
impl Parse for NameLitStr {
|
||||
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
||||
let string_literal: LitStr = input.parse()?;
|
||||
if let Ok(ident) = string_literal.parse() {
|
||||
Ok(NameLitStr(ident))
|
||||
} else {
|
||||
bail_spanned!(string_literal.span() => "expected a single identifier in double quotes")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for NameLitStr {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
self.0.to_tokens(tokens)
|
||||
}
|
||||
}
|
||||
|
||||
pub type ExtendsAttribute = KeywordAttribute<kw::extends, Path>;
|
||||
pub type FreelistAttribute = KeywordAttribute<kw::freelist, Box<Expr>>;
|
||||
pub type ModuleAttribute = KeywordAttribute<kw::module, LitStr>;
|
||||
pub type NameAttribute = KeywordAttribute<kw::name, NameLitStr>;
|
||||
pub type TextSignatureAttribute = KeywordAttribute<kw::text_signature, LitStr>;
|
||||
|
||||
impl<K: Parse + std::fmt::Debug, V: Parse> Parse for KeywordAttribute<K, V> {
|
||||
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
||||
let kw: K = input.parse()?;
|
||||
let _: Token![=] = input.parse()?;
|
||||
let value = input.parse()?;
|
||||
Ok(KeywordAttribute { kw, value })
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: ToTokens, V: ToTokens> ToTokens for KeywordAttribute<K, V> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
self.kw.to_tokens(tokens);
|
||||
Token![=](self.kw.span()).to_tokens(tokens);
|
||||
self.value.to_tokens(tokens);
|
||||
}
|
||||
}
|
||||
|
||||
pub type FromPyWithAttribute = KeywordAttribute<kw::from_py_with, LitStrValue<ExprPath>>;
|
||||
|
||||
/// For specifying the path to the pyo3 crate.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct CrateAttribute(pub Path);
|
||||
|
||||
impl Parse for CrateAttribute {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let _: Token![crate] = input.parse()?;
|
||||
let _: Token![=] = input.parse()?;
|
||||
let string_literal: LitStr = input.parse()?;
|
||||
string_literal.parse().map(CrateAttribute)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct TextSignatureAttribute {
|
||||
pub kw: kw::text_signature,
|
||||
pub eq_token: Token![=],
|
||||
pub lit: LitStr,
|
||||
}
|
||||
|
||||
impl Parse for TextSignatureAttribute {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
Ok(TextSignatureAttribute {
|
||||
kw: input.parse()?,
|
||||
eq_token: input.parse()?,
|
||||
lit: input.parse()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
pub type CrateAttribute = KeywordAttribute<Token![crate], LitStrValue<Path>>;
|
||||
|
||||
pub fn get_pyo3_options<T: Parse>(attr: &syn::Attribute) -> Result<Option<Punctuated<T, Comma>>> {
|
||||
if is_attribute_ident(attr, "pyo3") {
|
||||
|
|
|
@ -3,12 +3,14 @@ use quote::{quote_spanned, ToTokens};
|
|||
|
||||
pub enum Deprecation {
|
||||
CallAttribute,
|
||||
PyClassGcOption,
|
||||
}
|
||||
|
||||
impl Deprecation {
|
||||
fn ident(&self, span: Span) -> syn::Ident {
|
||||
let string = match self {
|
||||
Deprecation::CallAttribute => "CALL_ATTRIBUTE",
|
||||
Deprecation::PyClassGcOption => "PYCLASS_GC_OPTION",
|
||||
};
|
||||
syn::Ident::new(string, span)
|
||||
}
|
||||
|
@ -33,7 +35,10 @@ impl ToTokens for Deprecations {
|
|||
let ident = deprecation.ident(*span);
|
||||
quote_spanned!(
|
||||
*span =>
|
||||
let _ = _pyo3::impl_::deprecations::#ident;
|
||||
#[allow(clippy::let_unit_value)]
|
||||
{
|
||||
let _ = _pyo3::impl_::deprecations::#ident;
|
||||
}
|
||||
)
|
||||
.to_tokens(tokens)
|
||||
}
|
||||
|
|
|
@ -106,8 +106,9 @@ enum ContainerType<'a> {
|
|||
StructNewtype(&'a Ident),
|
||||
/// Tuple struct, e.g. `struct Foo(String)`.
|
||||
///
|
||||
/// Fields are extracted from a tuple.
|
||||
Tuple(usize),
|
||||
/// Variant contains a list of conversion methods for each of the fields that are directly
|
||||
/// extracted from the tuple.
|
||||
Tuple(Vec<FieldPyO3Attributes>),
|
||||
/// Tuple newtype, e.g. `#[transparent] struct Foo(String)`
|
||||
///
|
||||
/// The wrapped field is directly extracted from the object.
|
||||
|
@ -149,7 +150,15 @@ impl<'a> Container<'a> {
|
|||
(Fields::Unnamed(_), true) => ContainerType::TupleNewtype,
|
||||
(Fields::Unnamed(unnamed), false) => match unnamed.unnamed.len() {
|
||||
1 => ContainerType::TupleNewtype,
|
||||
len => ContainerType::Tuple(len),
|
||||
_ => {
|
||||
let fields = unnamed
|
||||
.unnamed
|
||||
.iter()
|
||||
.map(|field| FieldPyO3Attributes::from_attrs(&field.attrs))
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
ContainerType::Tuple(fields)
|
||||
}
|
||||
},
|
||||
(Fields::Named(named), true) => {
|
||||
let field = named
|
||||
|
@ -196,7 +205,7 @@ impl<'a> Container<'a> {
|
|||
match &self.ty {
|
||||
ContainerType::StructNewtype(ident) => self.build_newtype_struct(Some(ident)),
|
||||
ContainerType::TupleNewtype => self.build_newtype_struct(None),
|
||||
ContainerType::Tuple(len) => self.build_tuple_struct(*len),
|
||||
ContainerType::Tuple(tups) => self.build_tuple_struct(tups),
|
||||
ContainerType::Struct(tups) => self.build_struct(tups),
|
||||
}
|
||||
}
|
||||
|
@ -233,19 +242,35 @@ impl<'a> Container<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn build_tuple_struct(&self, len: usize) -> TokenStream {
|
||||
fn build_tuple_struct(&self, tups: &[FieldPyO3Attributes]) -> TokenStream {
|
||||
let self_ty = &self.path;
|
||||
let mut fields: Punctuated<TokenStream, syn::Token![,]> = Punctuated::new();
|
||||
for i in 0..len {
|
||||
let error_msg = format!("failed to extract field {}.{}", quote!(#self_ty), i);
|
||||
fields.push(quote!(
|
||||
s.get_item(#i).and_then(_pyo3::types::PyAny::extract).map_err(|inner| {
|
||||
let py = _pyo3::PyNativeType::py(obj);
|
||||
let new_err = _pyo3::exceptions::PyTypeError::new_err(#error_msg);
|
||||
new_err.set_cause(py, ::std::option::Option::Some(inner));
|
||||
new_err
|
||||
})?));
|
||||
for (index, attrs) in tups.iter().enumerate() {
|
||||
let error_msg = format!("failed to extract field {}.{}", quote!(#self_ty), index);
|
||||
|
||||
let parsed_item = match &attrs.from_py_with {
|
||||
None => quote!(
|
||||
obj.get_item(#index)?.extract()
|
||||
),
|
||||
Some(FromPyWithAttribute {
|
||||
value: expr_path, ..
|
||||
}) => quote! (
|
||||
#expr_path(obj.get_item(#index)?)
|
||||
),
|
||||
};
|
||||
|
||||
let extractor = quote!(
|
||||
#parsed_item.map_err(|inner| {
|
||||
let py = _pyo3::PyNativeType::py(obj);
|
||||
let new_err = _pyo3::exceptions::PyTypeError::new_err(#error_msg);
|
||||
new_err.set_cause(py, ::std::option::Option::Some(inner));
|
||||
new_err
|
||||
})?
|
||||
);
|
||||
|
||||
fields.push(quote!(#extractor));
|
||||
}
|
||||
let len = tups.len();
|
||||
let msg = if self.is_enum_variant {
|
||||
quote!(::std::format!(
|
||||
"expected tuple of length {}, but got length {}",
|
||||
|
@ -285,7 +310,9 @@ impl<'a> Container<'a> {
|
|||
new_err.set_cause(py, ::std::option::Option::Some(inner));
|
||||
new_err
|
||||
})?),
|
||||
Some(FromPyWithAttribute(expr_path)) => quote! (
|
||||
Some(FromPyWithAttribute {
|
||||
value: expr_path, ..
|
||||
}) => quote! (
|
||||
#expr_path(#get_field).map_err(|inner| {
|
||||
let py = _pyo3::PyNativeType::py(obj);
|
||||
let new_err = _pyo3::exceptions::PyTypeError::new_err(#conversion_error_msg);
|
||||
|
@ -323,7 +350,7 @@ enum ContainerPyO3Attribute {
|
|||
}
|
||||
|
||||
impl Parse for ContainerPyO3Attribute {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
||||
let lookahead = input.lookahead1();
|
||||
if lookahead.peek(attributes::kw::transparent) {
|
||||
let kw: attributes::kw::transparent = input.parse()?;
|
||||
|
@ -365,7 +392,7 @@ impl ContainerOptions {
|
|||
ContainerPyO3Attribute::Crate(path) => {
|
||||
ensure_spanned!(
|
||||
options.krate.is_none(),
|
||||
path.0.span() => "`crate` may only be provided once"
|
||||
path.span() => "`crate` may only be provided once"
|
||||
);
|
||||
options.krate = Some(path);
|
||||
}
|
||||
|
@ -396,7 +423,7 @@ enum FieldPyO3Attribute {
|
|||
}
|
||||
|
||||
impl Parse for FieldPyO3Attribute {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
||||
let lookahead = input.lookahead1();
|
||||
if lookahead.peek(attributes::kw::attribute) {
|
||||
let _: attributes::kw::attribute = input.parse()?;
|
||||
|
|
|
@ -19,9 +19,9 @@ pub struct ConstSpec {
|
|||
}
|
||||
|
||||
impl ConstSpec {
|
||||
pub fn python_name(&self) -> Cow<Ident> {
|
||||
pub fn python_name(&self) -> Cow<'_, Ident> {
|
||||
if let Some(name) = &self.attributes.name {
|
||||
Cow::Borrowed(&name.0)
|
||||
Cow::Borrowed(&name.value.0)
|
||||
} else {
|
||||
Cow::Owned(self.rust_ident.unraw())
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ pub enum PyO3ConstAttribute {
|
|||
}
|
||||
|
||||
impl Parse for PyO3ConstAttribute {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
||||
let lookahead = input.lookahead1();
|
||||
if lookahead.peek(attributes::kw::name) {
|
||||
input.parse().map(PyO3ConstAttribute::Name)
|
||||
|
@ -89,7 +89,7 @@ impl ConstAttributes {
|
|||
fn set_name(&mut self, name: NameAttribute) -> Result<()> {
|
||||
ensure_spanned!(
|
||||
self.name.is_none(),
|
||||
name.0.span() => "`name` may only be specified once"
|
||||
name.span() => "`name` may only be specified once"
|
||||
);
|
||||
self.name = Some(name);
|
||||
Ok(())
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) 2017-present PyO3 Project and Contributors
|
||||
//! This crate contains the implementation of the proc macro attributes
|
||||
|
||||
#![warn(elided_lifetimes_in_paths, unused_lifetimes)]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
|
||||
#![recursion_limit = "1024"]
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ use syn::ext::IdentExt;
|
|||
use syn::spanned::Spanned;
|
||||
use syn::Result;
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FnArg<'a> {
|
||||
pub name: &'a syn::Ident,
|
||||
pub by_ref: &'a Option<syn::token::Ref>,
|
||||
|
@ -157,14 +157,14 @@ impl SelfType {
|
|||
quote! {
|
||||
let _cell = #cell;
|
||||
let _ref = _cell.try_borrow()?;
|
||||
let _slf = &_ref;
|
||||
let _slf: &#cls = &*_ref;
|
||||
}
|
||||
}
|
||||
SelfType::Receiver { mutable: true } => {
|
||||
quote! {
|
||||
let _cell = #cell;
|
||||
let mut _ref = _cell.try_borrow_mut()?;
|
||||
let _slf = &mut _ref;
|
||||
let _slf: &mut #cls = &mut *_ref;
|
||||
}
|
||||
}
|
||||
SelfType::TryFromPyCell(span) => {
|
||||
|
@ -183,7 +183,7 @@ impl SelfType {
|
|||
pub enum CallingConvention {
|
||||
Noargs, // METH_NOARGS
|
||||
Varargs, // METH_VARARGS | METH_KEYWORDS
|
||||
Fastcall, // METH_FASTCALL | METH_KEYWORDS (Py3.7+ and !abi3)
|
||||
Fastcall, // METH_FASTCALL | METH_KEYWORDS (not compatible with `abi3` feature)
|
||||
TpNew, // special convention for tp_new
|
||||
}
|
||||
|
||||
|
@ -199,7 +199,8 @@ impl CallingConvention {
|
|||
} else if accept_kwargs {
|
||||
// for functions that accept **kwargs, always prefer varargs
|
||||
Self::Varargs
|
||||
} else if can_use_fastcall() {
|
||||
} else if cfg!(not(feature = "abi3")) {
|
||||
// Not available in the Stable ABI as of Python 3.10
|
||||
Self::Fastcall
|
||||
} else {
|
||||
Self::Varargs
|
||||
|
@ -207,13 +208,6 @@ impl CallingConvention {
|
|||
}
|
||||
}
|
||||
|
||||
fn can_use_fastcall() -> bool {
|
||||
const PY37: pyo3_build_config::PythonVersion =
|
||||
pyo3_build_config::PythonVersion { major: 3, minor: 7 };
|
||||
let config = pyo3_build_config::get();
|
||||
config.version >= PY37 && !config.abi3
|
||||
}
|
||||
|
||||
pub struct FnSpec<'a> {
|
||||
pub tp: FnType,
|
||||
// Rust function name
|
||||
|
@ -241,8 +235,15 @@ pub fn get_return_info(output: &syn::ReturnType) -> syn::Type {
|
|||
|
||||
pub fn parse_method_receiver(arg: &syn::FnArg) -> Result<SelfType> {
|
||||
match arg {
|
||||
syn::FnArg::Receiver(recv) => Ok(SelfType::Receiver {
|
||||
mutable: recv.mutability.is_some(),
|
||||
syn::FnArg::Receiver(
|
||||
recv @ syn::Receiver {
|
||||
reference: None, ..
|
||||
},
|
||||
) => {
|
||||
bail_spanned!(recv.span() => RECEIVER_BY_VALUE_ERR);
|
||||
}
|
||||
syn::FnArg::Receiver(syn::Receiver { mutability, .. }) => Ok(SelfType::Receiver {
|
||||
mutable: mutability.is_some(),
|
||||
}),
|
||||
syn::FnArg::Typed(syn::PatType { ty, .. }) => {
|
||||
if let syn::Type::ImplTrait(_) = &**ty {
|
||||
|
@ -273,7 +274,7 @@ impl<'a> FnSpec<'a> {
|
|||
ty: fn_type_attr,
|
||||
args: fn_attrs,
|
||||
mut python_name,
|
||||
} = parse_method_attributes(meth_attrs, name.map(|name| name.0), &mut deprecations)?;
|
||||
} = parse_method_attributes(meth_attrs, name.map(|name| name.value.0), &mut deprecations)?;
|
||||
|
||||
let (fn_type, skip_first_arg, fixed_convention) =
|
||||
Self::parse_fn_type(sig, fn_type_attr, &mut python_name)?;
|
||||
|
@ -420,7 +421,7 @@ impl<'a> FnSpec<'a> {
|
|||
}
|
||||
|
||||
pub fn default_value(&self, name: &syn::Ident) -> Option<TokenStream> {
|
||||
for s in self.attrs.iter() {
|
||||
for s in &self.attrs {
|
||||
match s {
|
||||
Argument::Arg(path, opt) | Argument::Kwarg(path, opt) => {
|
||||
if path.is_ident(name) {
|
||||
|
@ -437,7 +438,7 @@ impl<'a> FnSpec<'a> {
|
|||
}
|
||||
|
||||
pub fn is_pos_only(&self, name: &syn::Ident) -> bool {
|
||||
for s in self.attrs.iter() {
|
||||
for s in &self.attrs {
|
||||
if let Argument::PosOnlyArg(path, _) = s {
|
||||
if path.is_ident(name) {
|
||||
return true;
|
||||
|
@ -448,7 +449,7 @@ impl<'a> FnSpec<'a> {
|
|||
}
|
||||
|
||||
pub fn is_kw_only(&self, name: &syn::Ident) -> bool {
|
||||
for s in self.attrs.iter() {
|
||||
for s in &self.attrs {
|
||||
if let Argument::Kwarg(path, _) = s {
|
||||
if path.is_ident(name) {
|
||||
return true;
|
||||
|
@ -490,10 +491,12 @@ impl<'a> FnSpec<'a> {
|
|||
{
|
||||
use #krate as _pyo3;
|
||||
#deprecations
|
||||
_pyo3::callback::handle_panic(|#py| {
|
||||
let gil = _pyo3::GILPool::new();
|
||||
let #py = gil.python();
|
||||
_pyo3::callback::panic_result_into_callback_output(#py, ::std::panic::catch_unwind(move || -> _pyo3::PyResult<_> {
|
||||
#self_conversion
|
||||
#rust_call
|
||||
})
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -508,11 +511,13 @@ impl<'a> FnSpec<'a> {
|
|||
{
|
||||
use #krate as _pyo3;
|
||||
#deprecations
|
||||
_pyo3::callback::handle_panic(|#py| {
|
||||
let gil = _pyo3::GILPool::new();
|
||||
let #py = gil.python();
|
||||
_pyo3::callback::panic_result_into_callback_output(#py, ::std::panic::catch_unwind(move || -> _pyo3::PyResult<_> {
|
||||
#self_conversion
|
||||
#arg_convert
|
||||
#rust_call
|
||||
})
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -526,11 +531,13 @@ impl<'a> FnSpec<'a> {
|
|||
{
|
||||
use #krate as _pyo3;
|
||||
#deprecations
|
||||
_pyo3::callback::handle_panic(|#py| {
|
||||
let gil = _pyo3::GILPool::new();
|
||||
let #py = gil.python();
|
||||
_pyo3::callback::panic_result_into_callback_output(#py, ::std::panic::catch_unwind(move || -> _pyo3::PyResult<_> {
|
||||
#self_conversion
|
||||
#arg_convert
|
||||
#rust_call
|
||||
})
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -546,13 +553,15 @@ impl<'a> FnSpec<'a> {
|
|||
use #krate as _pyo3;
|
||||
#deprecations
|
||||
use _pyo3::callback::IntoPyCallbackOutput;
|
||||
_pyo3::callback::handle_panic(|#py| {
|
||||
let gil = _pyo3::GILPool::new();
|
||||
let #py = gil.python();
|
||||
_pyo3::callback::panic_result_into_callback_output(#py, ::std::panic::catch_unwind(move || -> _pyo3::PyResult<_> {
|
||||
#arg_convert
|
||||
let result = #rust_call;
|
||||
let initializer: _pyo3::PyClassInitializer::<#cls> = result.convert(#py)?;
|
||||
let cell = initializer.create_cell_from_subtype(#py, subtype)?;
|
||||
::std::result::Result::Ok(cell as *mut _pyo3::ffi::PyObject)
|
||||
})
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -727,3 +736,6 @@ fn parse_method_attributes(
|
|||
}
|
||||
|
||||
const IMPL_TRAIT_ERR: &str = "Python functions cannot have `impl Trait` arguments";
|
||||
const RECEIVER_BY_VALUE_ERR: &str =
|
||||
"Python objects are shared, so 'self' cannot be moved out of the Python interpreter.
|
||||
Try `&self`, `&mut self, `slf: PyRef<'_, Self>` or `slf: PyRefMut<'_, Self>`.";
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue