diff --git a/.github/workflows/doc-cn-sync.yml b/.github/workflows/doc-cn-sync.yml new file mode 100644 index 00000000000..99b7f302aea --- /dev/null +++ b/.github/workflows/doc-cn-sync.yml @@ -0,0 +1,39 @@ +name: Sync Translations on Doc Changes + +on: + push: + branches: [main] + paths: + - 'guide/**/*.md' # 监控guide下所有.md变化 + +jobs: + detect-changes: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 2 # 获取前一个commit以便diff + + - name: Find changed English docs + id: changes + run: | + # git diff找出变化的文件,排除zh-CN + CHANGED_FILES=$(git diff --name-only HEAD^ HEAD -- guide/**/*.md | grep -v '/zh-CN/' || true) + if [ -n "$CHANGED_FILES" ]; then + echo "changed=true" >> $GITHUB_OUTPUT + echo "files<> $GITHUB_OUTPUT + echo "$CHANGED_FILES" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + else + echo "changed=false" >> $GITHUB_OUTPUT + fi + + - name: Create issue for translation update + if: steps.changes.outputs.changed == 'true' + uses: peter-evans/create-issue-from-file@v5 # 这个action创建Issue,更灵活(GitHub会自动安装) + with: + title: "英文文档更新:需要同步中文翻译" + content: | + 以下英文文件有变化,请检查并更新对应中文版本(在guide/zh-CN/下): + + ${{ steps.changes.outputs.files }} \ No newline at end of file diff --git a/guide/cn/book.toml b/guide/cn/book.toml new file mode 100644 index 00000000000..e9c27691f8d --- /dev/null +++ b/guide/cn/book.toml @@ -0,0 +1,18 @@ +[book] +title = "PyO3 user guide" +description = "PyO3 user guide" +author = "PyO3 Project and Contributors" +language = "cn" +src = "../src/zh-CN" + +[preprocessor.pyo3_version] +command = "python3 guide/pyo3_version.py" + +[preprocessor.tabs] + +[output.html] +git-repository-url = "https://github.com/PyO3/pyo3/tree/main/guide" +edit-url-template = "https://github.com/PyO3/pyo3/edit/main/guide/{path}" +playground.runnable = false +additional-css = ["../theme/tabs.css"] +additional-js = ["../theme/tabs.js"] diff --git a/guide/book.toml b/guide/en/book.toml similarity index 78% rename from guide/book.toml rename to guide/en/book.toml index be682a64eab..22697d132e8 100644 --- a/guide/book.toml +++ b/guide/en/book.toml @@ -2,6 +2,8 @@ title = "PyO3 user guide" description = "PyO3 user guide" author = "PyO3 Project and Contributors" +language = "en" +src = "../src" [preprocessor.pyo3_version] command = "python3 guide/pyo3_version.py" @@ -12,5 +14,5 @@ command = "python3 guide/pyo3_version.py" git-repository-url = "https://github.com/PyO3/pyo3/tree/main/guide" edit-url-template = "https://github.com/PyO3/pyo3/edit/main/guide/{path}" playground.runnable = false -additional-css = ["theme/tabs.css"] -additional-js = ["theme/tabs.js"] +additional-css = ["../theme/tabs.css"] +additional-js = ["../theme/tabs.js"] diff --git a/guide/src/async-await.md b/guide/src/async-await.md index 9e53ca8dc7a..81d57ef3341 100644 --- a/guide/src/async-await.md +++ b/guide/src/async-await.md @@ -13,7 +13,7 @@ use pyo3::prelude::*; #[pyfunction] #[pyo3(signature=(seconds, result=None))] -async fn sleep(seconds: f64, result: Option) -> Option { +async fn sleep(seconds: f64, result: Option>) -> Option> { let (tx, rx) = oneshot::channel(); thread::spawn(move || { thread::sleep(Duration::from_secs_f64(seconds)); diff --git a/guide/src/class.md b/guide/src/class.md index 7d10dce9494..8eb71669d02 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -413,7 +413,7 @@ impl SubSubClass { } #[staticmethod] - fn factory_method(py: Python<'_>, val: usize) -> PyResult { + fn factory_method(py: Python<'_>, val: usize) -> PyResult> { let base = PyClassInitializer::from(BaseClass::new()); let sub = base.add_subclass(SubClass { val2: val }); if val % 2 == 0 { @@ -748,7 +748,7 @@ To create a constructor which takes a positional class argument, you can combine # use pyo3::prelude::*; # use pyo3::types::PyType; # #[pyclass] -# struct BaseClass(PyObject); +# struct BaseClass(Py); # #[pymethods] impl BaseClass { @@ -1385,7 +1385,12 @@ unsafe impl pyo3::type_object::PyTypeInfo for MyClass { #[inline] fn type_object_raw(py: pyo3::Python<'_>) -> *mut pyo3::ffi::PyTypeObject { ::lazy_type_object() - .get_or_init(py) + .get_or_try_init(py) + .unwrap_or_else(|e| pyo3::impl_::pyclass::type_object_init_failed( + py, + e, + ::NAME + )) .as_type_ptr() } } diff --git a/guide/src/class/protocols.md b/guide/src/class/protocols.md index e4ed8b6dae5..45a7126a9b2 100644 --- a/guide/src/class/protocols.md +++ b/guide/src/class/protocols.md @@ -59,7 +59,7 @@ given signatures should be interpreted as follows: #[pymethods] impl NotHashable { #[classattr] - const __hash__: Option = None; + const __hash__: Option> = None; } ``` @@ -162,7 +162,7 @@ use std::sync::Mutex; #[pyclass] struct MyIterator { - iter: Mutex + Send>>, + iter: Mutex> + Send>>, } #[pymethods] @@ -170,7 +170,7 @@ impl MyIterator { fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> { slf } - fn __next__(slf: PyRefMut<'_, Self>) -> Option { + fn __next__(slf: PyRefMut<'_, Self>) -> Option> { slf.iter.lock().unwrap().next() } } @@ -283,7 +283,7 @@ Use the `#[pyclass(sequence)]` annotation to instruct PyO3 to fill the `sq_lengt #[pymethods] impl NoContains { #[classattr] - const __contains__: Option = None; + const __contains__: Option> = None; } ``` @@ -439,7 +439,7 @@ use pyo3::gc::PyVisit; #[pyclass] struct ClassWithGCSupport { - obj: Option, + obj: Option>, } #[pymethods] diff --git a/guide/src/conversions/tables.md b/guide/src/conversions/tables.md index 7fc09657aff..9c2bd0add1f 100644 --- a/guide/src/conversions/tables.md +++ b/guide/src/conversions/tables.md @@ -1,6 +1,6 @@ ## Mapping of Rust types to Python types -When writing functions callable from Python (such as a `#[pyfunction]` or in a `#[pymethods]` block), the trait `FromPyObject` is required for function arguments, and `IntoPy` is required for function return values. +When writing functions callable from Python (such as a `#[pyfunction]` or in a `#[pymethods]` block), the trait `FromPyObject` is required for function arguments, and `IntoPyObject` is required for function return values. Consult the tables in the following section to find the Rust types provided by PyO3 which implement these traits. @@ -54,7 +54,6 @@ It is also worth remembering the following special types: | `Python<'py>` | A GIL token, used to pass to PyO3 constructors to prove ownership of the GIL. | | `Bound<'py, T>` | A Python object connected to the GIL lifetime. This provides access to most of PyO3's APIs. | | `Py` | A Python object isolated from the GIL lifetime. This can be sent to other threads. | -| `PyObject` | An alias for `Py` | | `PyRef` | A `#[pyclass]` borrowed immutably. | | `PyRefMut` | A `#[pyclass]` borrowed mutably. | diff --git a/guide/src/conversions/traits.md b/guide/src/conversions/traits.md index a7c8d03bd4b..704a43d9aab 100644 --- a/guide/src/conversions/traits.md +++ b/guide/src/conversions/traits.md @@ -580,7 +580,7 @@ forward the implementation to the inner type. // newtype tuple structs are implicitly `transparent` #[derive(IntoPyObject)] -struct TransparentTuple(PyObject); +struct TransparentTuple(Py); #[derive(IntoPyObject)] #[pyo3(transparent)] @@ -599,7 +599,7 @@ For `enum`s each variant is converted according to the rules for `struct`s above #[derive(IntoPyObject)] enum Enum<'a, 'py, K: Hash + Eq, V> { // enums are supported and convert using the same - TransparentTuple(PyObject), // rules on the variants as the structs above + TransparentTuple(Py), // rules on the variants as the structs above #[pyo3(transparent)] TransparentStruct { inner: Bound<'py, PyAny> }, Tuple(&'a str, HashMap), @@ -645,7 +645,7 @@ demonstrated below. ```rust,no_run # use pyo3::prelude::*; # #[allow(dead_code)] -struct MyPyObjectWrapper(PyObject); +struct MyPyObjectWrapper(Py); impl<'py> IntoPyObject<'py> for MyPyObjectWrapper { type Target = PyAny; // the Python type @@ -741,7 +741,6 @@ In the example above we used `BoundObject::into_any` and `BoundObject::unbind` t [`FromPyObject`]: {{#PYO3_DOCS_URL}}/pyo3/conversion/trait.FromPyObject.html [`IntoPyObject`]: {{#PYO3_DOCS_URL}}/pyo3/conversion/trait.IntoPyObject.html [`IntoPyObjectExt`]: {{#PYO3_DOCS_URL}}/pyo3/conversion/trait.IntoPyObjectExt.html -[`PyObject`]: {{#PYO3_DOCS_URL}}/pyo3/type.PyObject.html [`PyRef`]: {{#PYO3_DOCS_URL}}/pyo3/pycell/struct.PyRef.html [`PyRefMut`]: {{#PYO3_DOCS_URL}}/pyo3/pycell/struct.PyRefMut.html diff --git a/guide/src/free-threading.md b/guide/src/free-threading.md index 11e805c538d..f00b6afdb94 100644 --- a/guide/src/free-threading.md +++ b/guide/src/free-threading.md @@ -164,7 +164,7 @@ simultaneously interacting with the interpreter. You still need to obtain a `'py` lifetime is to interact with Python objects or call into the CPython C API. If you are not yet attached to the Python runtime, you can register a thread using the [`Python::attach`] -function. Threads created via the Python [`threading`] module do not not need to +function. Threads created via the Python [`threading`] module do not need to do this, and pyo3 will handle setting up the [`Python<'py>`] token when CPython calls into your extension. diff --git a/guide/src/function/signature.md b/guide/src/function/signature.md index 913e9d4c3ec..2f578366782 100644 --- a/guide/src/function/signature.md +++ b/guide/src/function/signature.md @@ -82,7 +82,7 @@ Arguments of type `Python` must not be part of the signature: # use pyo3::prelude::*; #[pyfunction] #[pyo3(signature = (lambda))] -pub fn simple_python_bound_function(py: Python<'_>, lambda: PyObject) -> PyResult<()> { +pub fn simple_python_bound_function(py: Python<'_>, lambda: Py) -> PyResult<()> { Ok(()) } ``` diff --git a/guide/src/migration.md b/guide/src/migration.md index 9306525cf11..4a48dbfabef 100644 --- a/guide/src/migration.md +++ b/guide/src/migration.md @@ -16,6 +16,14 @@ For this reason we chose to rename these to more modern terminology introduced i - `pyo3::prepare_freethreaded_python` is now called `Python::initialize`. +### Deprecation of `PyObject` type alias +
+Click to expand + +The type alias `PyObject` (aka `Py`) is often confused with the identically named FFI definition `pyo3::ffi::PyObject`. For this reason we are deprecating its usage. To migrate simply replace its usage by the target type `Py`. + +
+ ### Deprecation of `GILProtected`
Click to expand @@ -186,6 +194,7 @@ impl ToPyObject for MyPyObjectWrapper { After: ```rust,no_run +# #![allow(deprecated)] # use pyo3::prelude::*; # #[allow(dead_code)] # struct MyPyObjectWrapper(PyObject); diff --git a/guide/src/python-from-rust/function-calls.md b/guide/src/python-from-rust/function-calls.md index ed8454fe431..6fde4cda33e 100644 --- a/guide/src/python-from-rust/function-calls.md +++ b/guide/src/python-from-rust/function-calls.md @@ -12,9 +12,9 @@ Both of these APIs take `args` and `kwargs` arguments (for positional and keywor * [`call1`]({{#PYO3_DOCS_URL}}/pyo3/types/trait.PyAnyMethods.html#tymethod.call1) and [`call_method1`]({{#PYO3_DOCS_URL}}/pyo3/types/trait.PyAnyMethods.html#tymethod.call_method1) to call only with positional `args`. * [`call0`]({{#PYO3_DOCS_URL}}/pyo3/types/trait.PyAnyMethods.html#tymethod.call0) and [`call_method0`]({{#PYO3_DOCS_URL}}/pyo3/types/trait.PyAnyMethods.html#tymethod.call_method0) to call with no arguments. -For convenience the [`Py`](../types.md#pyt-and-pyobject) smart pointer also exposes these same six API methods, but needs a `Python` token as an additional first argument to prove the GIL is held. +For convenience the [`Py`](../types.md#pyt) smart pointer also exposes these same six API methods, but needs a `Python` token as an additional first argument to prove the GIL is held. -The example below calls a Python function behind a `PyObject` (aka `Py`) reference: +The example below calls a Python function behind a `Py` reference: ```rust use pyo3::prelude::*; diff --git a/guide/src/python-typing-hints.md b/guide/src/python-typing-hints.md index 76ad0b6aa21..d2c755b0712 100644 --- a/guide/src/python-typing-hints.md +++ b/guide/src/python-typing-hints.md @@ -230,7 +230,7 @@ impl MyClass { pub fn __class_getitem__( cls: &Bound<'_, PyType>, key: &Bound<'_, PyAny>, - ) -> PyResult { + ) -> PyResult> { /* implementation details */ } } diff --git a/guide/src/trait-bounds.md b/guide/src/trait-bounds.md index 0f14d8e3783..3ac56b68035 100644 --- a/guide/src/trait-bounds.md +++ b/guide/src/trait-bounds.md @@ -44,7 +44,7 @@ How could we expose this solver to Python thanks to PyO3 ? ## Implementation of the trait bounds for the Python class If a Python class implements the same three methods as the `Model` trait, it seems logical it could be adapted to use the solver. -However, it is not possible to pass a `PyObject` to it as it does not implement the Rust trait (even if the Python model has the required methods). +However, it is not possible to pass a `Py` to it as it does not implement the Rust trait (even if the Python model has the required methods). In order to implement the trait, we must write a wrapper around the calls in Rust to the Python model. The method signatures must be the same as the trait, keeping in mind that the Rust trait cannot be changed for the purpose of making the code available in Python. diff --git a/guide/src/types.md b/guide/src/types.md index 891d639e02e..50d98aac41e 100644 --- a/guide/src/types.md +++ b/guide/src/types.md @@ -22,9 +22,9 @@ The recommendation of when to use each of these smart pointers is as follows: The sections below also explain these smart pointers in a little more detail. -### `Py` (and `PyObject`) +### `Py` -[`Py`][Py] is the foundational smart pointer in PyO3's API. The type parameter `T` denotes the type of the Python object. Very frequently this is `PyAny`, meaning any Python object. This is so common that `Py` has a type alias `PyObject`. +[`Py`][Py] is the foundational smart pointer in PyO3's API. The type parameter `T` denotes the type of the Python object. Very frequently this is `PyAny`, meaning any Python object. Because `Py` is not bound to [the `'py` lifetime](./python-from-rust.md#the-py-lifetime), it is the type to use when storing a Python object inside a Rust `struct` or `enum` which do not want to have a lifetime parameter. In particular, [`#[pyclass]`][pyclass] types are not permitted to have a lifetime, so `Py` is the correct type to store Python objects inside them. @@ -117,11 +117,11 @@ fn add<'py>( # }) ``` -If naming the `'py` lifetime adds unwanted complexity to the function signature, it is also acceptable to return `PyObject` (aka `Py`), which has no lifetime. The cost is instead paid by a slight increase in implementation complexity, as seen by the introduction of a call to [`Bound::unbind`]: +If naming the `'py` lifetime adds unwanted complexity to the function signature, it is also acceptable to return `Py`, which has no lifetime. The cost is instead paid by a slight increase in implementation complexity, as seen by the introduction of a call to [`Bound::unbind`]: ```rust # use pyo3::prelude::*; -fn add(left: &Bound<'_, PyAny>, right: &Bound<'_, PyAny>) -> PyResult { +fn add(left: &Bound<'_, PyAny>, right: &Bound<'_, PyAny>) -> PyResult> { let output: Bound<'_, PyAny> = left.add(right)?; Ok(output.unbind()) } diff --git a/guide/src/zh-CN/SUMMARY.md b/guide/src/zh-CN/SUMMARY.md new file mode 100644 index 00000000000..d8a49b95712 --- /dev/null +++ b/guide/src/zh-CN/SUMMARY.md @@ -0,0 +1,52 @@ +# 摘要 + +[介绍](index.md) + +--- + +- [入门指南](getting-started.md) +- [在 Python 调用 Rust](rust-from-python.md) + - [Python 模块](module.md) + - [Python 函数](function.md) + - [函数签名](function/signature.md) + - [错误处理](function/error-handling.md) + - [Python 类](class.md) + - [类自定义](class/protocols.md) + - [基本对象自定义](class/object.md) + - [模拟数值类型](class/numeric.md) + - [模拟可调用对象](class/call.md) + - [线程安全](class/thread-safety.md) +- [在 Rust 调用 Python](python-from-rust.md) + - [Python 对象类型](types.md) + - [Python 异常](exception.md) + - [调用 Python 函数](python-from-rust/function-calls.md) + - [执行现有的 Python 代码](python-from-rust/calling-existing-code.md) +- [类型转换](conversions.md) + - [Rust 类型到 Python 类型的映射](conversions/tables.md) + - [转换 trait](conversions/traits.md) +- [使用 `async` 和 `await`](async-await.md) +- [并行性](parallelism.md) +- [支持自由线程的 CPython](free-threading.md) +- [调试](debugging.md) +- [功能参考](features.md) +- [性能](performance.md) +- [类型存根生成和内省](type-stub.md) +- [高级主题](advanced.md) +- [构建和分发](building-and-distribution.md) + - [支持多个 Python 版本](building-and-distribution/multiple-python-versions.md) +- [实用的 crate](ecosystem.md) + - [日志记录](ecosystem/logging.md) + - [跟踪](ecosystem/tracing.md) + - [使用 `async` 和 `await`](ecosystem/async-await.md) +- [常见问题和故障排除](faq.md) + +--- + +[附录 A: 迁移指南](migration.md) +[附录 B: Trait 边界](trait-bounds.md) +[附录 C: Python 类型提示](python-typing-hints.md) +[变更日志](changelog.md) + +--- + +[贡献](contributing.md) \ No newline at end of file diff --git a/guide/src/zh-CN/advanced.md b/guide/src/zh-CN/advanced.md new file mode 100644 index 00000000000..76a37ee98b5 --- /dev/null +++ b/guide/src/zh-CN/advanced.md @@ -0,0 +1,5 @@ +# 高级主题 + +**翻译即将到来。请参阅英文版本:(https://pyo3.rs/main/advanced.html)** + + \ No newline at end of file diff --git a/guide/src/zh-CN/async-await.md b/guide/src/zh-CN/async-await.md new file mode 100644 index 00000000000..9db057abe37 --- /dev/null +++ b/guide/src/zh-CN/async-await.md @@ -0,0 +1,5 @@ +# 使用 `async` 和 `await` + +**翻译即将到来。请参阅英文版本:(https://pyo3.rs/main/async-await.html)** + + \ No newline at end of file diff --git a/guide/src/zh-CN/building-and-distribution.md b/guide/src/zh-CN/building-and-distribution.md new file mode 100644 index 00000000000..b0171d5107c --- /dev/null +++ b/guide/src/zh-CN/building-and-distribution.md @@ -0,0 +1,5 @@ +# 构建和分发 + +**翻译即将到来。请参阅英文版本:(https://pyo3.rs/main/building-and-distribution.html)** + + \ No newline at end of file diff --git a/guide/src/zh-CN/building-and-distribution/multiple-python-versions.md b/guide/src/zh-CN/building-and-distribution/multiple-python-versions.md new file mode 100644 index 00000000000..d318212c764 --- /dev/null +++ b/guide/src/zh-CN/building-and-distribution/multiple-python-versions.md @@ -0,0 +1,5 @@ +# 支持多个 Python 版本 + +**翻译即将到来。请参阅英文版本:(https://pyo3.rs/main/building-and-distribution/multiple-python-versions.html)** + + \ No newline at end of file diff --git a/guide/src/zh-CN/changelog.md b/guide/src/zh-CN/changelog.md new file mode 100644 index 00000000000..bd0b96b2773 --- /dev/null +++ b/guide/src/zh-CN/changelog.md @@ -0,0 +1,5 @@ +# 变更日志 + +**翻译即将到来。请参阅英文版本:(https://pyo3.rs/main/changelog.html)** + + \ No newline at end of file diff --git a/guide/src/zh-CN/class.md b/guide/src/zh-CN/class.md new file mode 100644 index 00000000000..b32ac1d84b4 --- /dev/null +++ b/guide/src/zh-CN/class.md @@ -0,0 +1,5 @@ +# Python 类 + +**翻译即将到来。请参阅英文版本:(https://pyo3.rs/main/class.html)** + + \ No newline at end of file diff --git a/guide/src/zh-CN/class/call.md b/guide/src/zh-CN/class/call.md new file mode 100644 index 00000000000..7cc82a512e0 --- /dev/null +++ b/guide/src/zh-CN/class/call.md @@ -0,0 +1,5 @@ +# 模拟可调用对象 + +**翻译即将到来。请参阅英文版本:(https://pyo3.rs/main/class/call.html)** + + \ No newline at end of file diff --git a/guide/src/zh-CN/class/numeric.md b/guide/src/zh-CN/class/numeric.md new file mode 100644 index 00000000000..4a4c48f05bd --- /dev/null +++ b/guide/src/zh-CN/class/numeric.md @@ -0,0 +1,5 @@ +# 模拟数值类型 + +**翻译即将到来。请参阅英文版本:(https://pyo3.rs/main/class/numeric.html)** + + \ No newline at end of file diff --git a/guide/src/zh-CN/class/object.md b/guide/src/zh-CN/class/object.md new file mode 100644 index 00000000000..331f6929a98 --- /dev/null +++ b/guide/src/zh-CN/class/object.md @@ -0,0 +1,5 @@ +# 基本对象自定义 + +**翻译即将到来。请参阅英文版本:(https://pyo3.rs/main/class/object.html)** + + \ No newline at end of file diff --git a/guide/src/zh-CN/class/protocols.md b/guide/src/zh-CN/class/protocols.md new file mode 100644 index 00000000000..6aeae75ccaa --- /dev/null +++ b/guide/src/zh-CN/class/protocols.md @@ -0,0 +1,5 @@ +# 类自定义 + +**翻译即将到来。请参阅英文版本:(https://pyo3.rs/main/class/protocols.html)** + + \ No newline at end of file diff --git a/guide/src/zh-CN/class/thread-safety.md b/guide/src/zh-CN/class/thread-safety.md new file mode 100644 index 00000000000..a1c21b49c45 --- /dev/null +++ b/guide/src/zh-CN/class/thread-safety.md @@ -0,0 +1,5 @@ +# 线程安全 + +**翻译即将到来。请参阅英文版本:(https://pyo3.rs/main/class/thread-safety.html)** + + \ No newline at end of file diff --git a/guide/src/zh-CN/contributing.md b/guide/src/zh-CN/contributing.md new file mode 100644 index 00000000000..d126efb9782 --- /dev/null +++ b/guide/src/zh-CN/contributing.md @@ -0,0 +1,249 @@ +# 贡献指南 + +感谢您对贡献 PyO3 的兴趣!欢迎所有人 - 请考虑阅读我们的[行为准则](https://github.com/PyO3/pyo3/blob/main/Code-of-Conduct.md)以维护社区的积极性和包容性。 + +若您正在寻找贡献方向,请参阅[“开始贡献”](#开始贡献)部分。若已有具体议题需开发,并需要有关开发过程的信息,[“撰写拉取请求”](#撰写拉取请求)部分将提供流程指导。 + +如果您想熟悉代码库,请参阅[Architecture.md](https://github.com/PyO3/pyo3/blob/main/Architecture.md)。 + +## 开始贡献 + +请加入您对 PyO3 感兴趣的的任何部分。我们使用 GitHub 问题来记录所有错误和想法。如果您想处理某个具体问题,请随时请求将其分配给您。 + +以下部分将包括具体的贡献方向建议。 + +## 设置开发环境 + +为了使用和开发 PyO3,您需要在系统上安装 Python 和 Rust。 +* 我们推荐使用 [rustup](https://rustup.rs/) 灵活管理项目所需的 Rust 工具链。 +* 强烈建议通过 [Pyenv](https://github.com/pyenv/pyenv) 选择特定 Python 版本。。 +* 可搭配 [virtualenv](https://virtualenv.pypa.io/en/latest/) (独立或与 Pyenv 协同)调用指定 Python 版本。 +* 自动化 CI 任务依赖 [`nox`][nox] 工具实现。 + +### 帮助用户识别错误 + +[PyO3 Discord 服务器](https://discord.gg/33kcChzH7f) 非常活跃,有许多 PyO3 新用户,而且往往完全是 Rust 新手。帮助他们调试是获得 PyO3 代码库经验的好方法。 + +帮助他人往往会揭示当前错误、文档弱点和缺失的 API。建议立即为这些创建 GitHub Issues,以便解决方案可以被设计和实现! + +### 实现准备开发的 issue + +解决方案明确但尚未实现的 issue 使用 [needs-implementer](https://github.com/PyO3/pyo3/issues?q=is%3Aissue+is%3Aopen+label%3Aneeds-implementer) 标签。 + +如果您对解决方案感到困惑,不用担心!PyO3 的核心贡献者将很乐意指导您解答任何问题,以帮助您编写解决方案。 + +### 帮助编写优秀的文档 + +PyO3 提供采用 mdBook 构建的用户指南以及常规 Rust API 文档。两者均致力于实现详实准确、通俗易懂且及时更新。我们始终欢迎提交 PR 以修复拼写错误、优化措辞、补充示例等。 + +当前需要重点支持的文档工作领域: + +- 请求文档改进的 issue 使用 [documentation](https://github.com/PyO3/pyo3/issues?q=is%3Aissue+is%3Aopen+label%3Adocumentation) 标签跟踪。 +- 并非所有 API 在创建时都有文档或示例。我们的目标是为所有 PyO3 API 提供文档 [](https://github.com/PyO3/pyo3/issues/306)。如果您看到 API 缺少文档,可以补充编写并打开 PR! + +如需构建文档(包括所有功能),请安装 [`nox`][nox] 然后运行 + +```shell +nox -s docs -- open +``` + +#### 文档测试 + +我们在文档中使用了许多代码块。在进行更改时运行 `cargo test --doc` 来检查文档测试是否仍然有效,或者 `cargo test` 来运行所有 Rust 测试,包括文档测试。请参阅 https://doc.rust-lang.org/rustdoc/documentation-tests.html 获取文档测试指南。 + +#### 构建指南 + +您可以使用 `mdbook` 在本地构建用户指南来预览它。 + +首先,安装 [`mdbook`][mdbook]、[`mdbook-tabs`][mdbook-tabs] 插件和 [`nox`][nox]。然后,运行 + +```shell +nox -s build-guide -- --open +``` + +如需检查指南中的所有链接是否有效,请安装 [`lychee`][lychee] 并使用 `check-guide` 会话: + +```shell +nox -s check-guide +``` + +### 帮助设计下一个 PyO3 + +尚未有明确解决方案的 issue 使用 [needs-design](https://github.com/PyO3/pyo3/issues?q=is%3Aissue+is%3Aopen+label%3Aneeds-design) 标签。 + +若您对以上任何议题感兴趣,​欢迎加入相关议题的讨论​!所有意见都备受重视。若您愿意进一步参与(例如通过草稿PR尝试API设计),那会更棒! + +### 审查拉取请求 + +欢迎每个人在开放的 PR 上提交评论。请帮助确保新的 PyO3 API 安全、高性能、整洁且易用! + +## 撰写拉取请求 + +在编写 PR 时要注意的几件事。 + +### 测试和持续集成 + +PyO3 仓库使用 GitHub Actions。如果 CI 不成功,PR 将被阻止合并。对所有 Rust 和 Python 代码检查格式化、linting 和测试。此外,Rust 代码中的所有警告都被禁止(使用 `RUSTFLAGS="-D warnings"`)。 + +测试使用所有支持的 Python 版本与最新的稳定 Rust 编译器运行,以及 Python 3.9 与最低支持的 Rust 版本。 + +如果您添加新功能,您应该将其添加到我们的 *Cargo.toml* 中的 `full` 功能,以便在 CI 中测试。 + +您可以使用 `nox` 自己运行这些检查。使用 `nox -l` 列出您可以运行的完整子命令集。 + +#### Linting Python 代码 +`nox -s ruff` + +#### Linting Rust 代码 +`nox -s rustfmt` + +#### Semver 检查 +`cargo semver-checks check-release` + +#### Clippy +`nox -s clippy-all` + +#### 测试 +`nox -s test` 或仅 Rust 测试的 `cargo test`,仅 Python 测试的 `nox -f pytests/noxfile.py -s test` + +#### 检查所有条件编译 +`nox -s check-feature-powerset` + +#### UI 测试 + +PyO3 使用 [`trybuild`](https://github.com/dtolnay/trybuild) 开发 UI 测试,以捕获 Rust 编译器的一些宏功能的错误消息。 + +因为这些 UI 测试有几个功能组合,当更新它们所有(例如对于新的 Rust 编译器版本)时,使用 `update-ui-tests` nox 会话可能会有帮助: + +```bash +nox -s update-ui-tests +``` + +### 文档更改 + +我们使用 [towncrier](https://towncrier.readthedocs.io/en/stable/index.html) 为每个发布生成 CHANGELOG。 + +要在发布笔记中包含您的更改,您应该在 `newsfragments` 目录中创建一个(或多个)新闻项。有效的新闻项应保存为 `..md`,其中 `` 是拉取请求编号,`` 是以下之一: +- `packaging` - 用于依赖更改和 Python / Rust 版本兼容性更改 +- `added` - 用于新功能 +- `changed` - 用于已存在但已被更改或弃用的功能 +- `removed` - 用于已移除的功能 +- `fixed` - 用于被分类为 bug 修复的“changed”功能 + +仅文档 PR 不需要新闻项;以 `docs:` 开头您的 PR 标题以跳过检查。 + +### 风格指南 + +#### 泛型代码 + +PyO3 有许多泛型 API 来提高可用性。这些可能以泛型代码膨胀为代价。在合理的情况下,尝试实现泛型函数的具体子部分。有两种形式: + +- 如果具体子部分不受益于其他函数的重用,将其命名为 `inner` 并保持为函数的局部。 +- 如果具体子部分被其他函数重用,最好将其命名为 `_foo` 并放置在源代码中 `foo` 的正下方(其中 `foo` 是原始泛型函数)。 + +#### FFI 调用 + +PyO3 使用原始指针对 Python 的 C API 进行许多 FFI 调用。在可能的情况下,尝试避免在表达式中使用指向临时变量的指针: + +```rust +// dangerous +pyo3::ffi::Something(name.to_object(py).as_ptr()); + +// because the following refactoring is a use-after-free error: +let name = name.to_object(py).as_ptr(); +pyo3::ffi::Something(name) +``` + +相反,优先绑定安全的拥有的 `PyObject` 包装器,然后传递给 FFI 函数: + +```rust +let name: PyObject = name.to_object(py); +pyo3::ffi::Something(name.as_ptr()) +// name will automatically be freed when it falls out of scope +``` + +## Python 和 Rust 版本支持政策 + +PyO3 致力于充分保持兼容性,确保使用 PyO3 构建的 Python 扩展能够在大多数常见软件包管理器上顺利部署。 + +为尽可能减轻软件包维护者的工作负担,PyO3 承诺在可行范围内,​仅当同步调整最低支持的 Rust 和 Python 版本时,才会更新兼容性要求。此类调整仅发生在 0.x 版本发布中(更新频率约为每年一次),且仅在旧版 Python 结束生命周期后实施。(具体时间表请参考 Python 官方生命周期日历:https://endoflife.date/python ) + +以下列示了 PR 的语言兼容性指南。 + +### Python + +PyO3 支持所有官方支持的 Python 版本,以及最新的 PyPy3 发布。所有这些版本都在 CI 中测试。 + +#### 添加对新 CPython 版本的支持 + +如果您计划添加对 CPython 预发布版本的支持,这里是一个(非详尽的)检查列表: + + - [ ] 等到最后一个 alpha 发布(通常是 alpha7),因为 ABI 直到第一个 beta 发布才保证 + - [ ] 将 prelease_ver-dev(例如 `3.14-dev`)添加到 `.github/workflows/ci.yml`,并在 `noxfile.py`、`pyo3-ffi/Cargo.toml` 中的 `[package.metadata.cpython]` 下的 `max-version` 和 `pyo3-ffi/build.rs` 中的 `max` 中提升版本 +- [ ] 为版本添加新的 abi3-prerelease 功能(例如 `abi3-py314`) + - 在 `pyo3-build-config/Cargo.toml` 中,将 abi3-most_current_stable 设置为 ["abi3-prerelease"],abi3-prerelease 设置为 ["abi3"] + - 在 `pyo3-ffi/Cargo.toml` 中,将 abi3-most_current_stable 设置为 ["abi3-prerelease", "pyo3-build-config/abi3-most_current_stable"],abi3-prerelease 设置为 ["abi3", "pyo3-build-config/abi3-prerelease"] + - 在 `Cargo.toml` 中,将 abi3-most_current_stable 设置为 ["abi3-prerelease", "pyo3-ffi/abi3-most_current_stable"],abi3-prerelease 设置为 ["abi3", "pyo3-ffi/abi3-prerelease"] + - [ ] 使用 `#[cfg(Py_prerelease)]`(例如 `#[cfg(Py_3_14)]`)和 `#[cfg(not(Py_prerelease))]` 来指示 CPython 稳定分支和预发布之间的更改 + - [ ] 不要为 CPython 头文件中以 `_` 前缀的任何函数、结构体或全局变量添加 Rust 绑定 + - [ ] Ping @ngoldbaum 和 @davidhewitt 以获取帮助 + +### Rust + +PyO3 致力于充分利用 Rust 语言的最新特性,确保底层实现始终保持最高效率。 + +所支持的最低 Rust 版本将在升级 Python 和 Rust 依赖版本时确定。具体策略是:将最低 Rust 版本设置为不超过当前 Debian、RHEL 和 Alpine Linux 发行版预装的最低 Rust 版本。 + +持续集成(CI)系统会同步测试最新的稳定 Rust 版本和已确定的最低支持版本。得益于 Rust 的稳定性保障机制,此策略可确保所有中间版本均获得兼容性支持。 + +## 基准测试 + +PyO3 提供两套基准测试方案,用于评估关键性能指标。当前测试覆盖范围有限,​欢迎通过 PR 补充新测试用例以扩展该体系! + +首先,有位于 `pyo3-benches` 子目录中的基于 Rust 的基准测试。您可以使用以下命令运行这些基准测试: + + nox -s bench + +其次,在 `pytests` 子目录中有一个基于 Python 的基准测试。您可以[在这里](https://github.com/PyO3/pyo3/tree/main/pytests)阅读更多关于它的信息。 + +## 代码覆盖率 + +您可以查看 PyO3 测试覆盖和未覆盖的代码。我们旨在达到 100% 覆盖率 - 如果您注意到缺乏覆盖,请检查覆盖率并添加测试! + +- 首先,确保安装了 llvm-cov cargo 插件。在与 `nox` 一起使用之前,您可能需要通过 cargo 运行一次插件。 +```shell +cargo install cargo-llvm-cov +cargo llvm-cov +``` +- 然后,使用以下命令生成 `lcov.info` 文件 +```shell +nox -s coverage -- lcov +``` +您可以安装 IDE 插件来查看覆盖率。例如,如果您使用 VSCode: +- 添加 [coverage-gutters](https://marketplace.visualstudio.com/items?itemName=ryanluker.vscode-coverage-gutters) 插件。 +- 将这些设置添加到 VSCode 的 `settings.json`: +```json +{ + "coverage-gutters.coverageFileNames": [ + "lcov.info", + "cov.xml", + "coverage.xml", + ], + "coverage-gutters.showLineCoverage": true +} +``` +- 您现在应该能够看到测试代码的绿色突出显示,以及未测试代码的红色突出显示。 + +## 赞助此项目 + +目前没有官方组织代表 PyO3 接受赞助。如果您寻求为 PyO3 生态系统提供大量资金,请在 [GitHub](https://github.com/PyO3/pyo3/issues/new) 或 [Discord](https://discord.gg/33kcChzH7f) 联系我们,我们可以讨论。 + +与此同时,我们的一些维护者有个人 GitHub 赞助页面,社区将非常感激您的支持: + +- [davidhewitt](https://github.com/sponsors/davidhewitt) +- [messense](https://github.com/sponsors/messense) + +[mdbook]: https://rust-lang.github.io/mdBook/cli/index.html +[mdbook-tabs]: https://mdbook-plugins.rustforweb.org/tabs.html +[lychee]: https://github.com/lycheeverse/lychee +[nox]: https://github.com/theacodes/nox \ No newline at end of file diff --git a/guide/src/zh-CN/conversions.md b/guide/src/zh-CN/conversions.md new file mode 100644 index 00000000000..499102c04e1 --- /dev/null +++ b/guide/src/zh-CN/conversions.md @@ -0,0 +1,5 @@ +# 类型转换 + +**翻译即将到来。请参阅英文版本:(https://pyo3.rs/main/conversions.html)** + + \ No newline at end of file diff --git a/guide/src/zh-CN/conversions/tables.md b/guide/src/zh-CN/conversions/tables.md new file mode 100644 index 00000000000..b7fe90588ed --- /dev/null +++ b/guide/src/zh-CN/conversions/tables.md @@ -0,0 +1,5 @@ +# Rust 类型到 Python 类型的映射 + +**翻译即将到来。请参阅英文版本:(https://pyo3.rs/main/conversions/tables.html)** + + \ No newline at end of file diff --git a/guide/src/zh-CN/conversions/traits.md b/guide/src/zh-CN/conversions/traits.md new file mode 100644 index 00000000000..725aba90d74 --- /dev/null +++ b/guide/src/zh-CN/conversions/traits.md @@ -0,0 +1,5 @@ +# 转换 trait + +**翻译即将到来。请参阅英文版本:(https://pyo3.rs/main/conversions/traits.html)** + + \ No newline at end of file diff --git a/guide/src/zh-CN/debugging.md b/guide/src/zh-CN/debugging.md new file mode 100644 index 00000000000..803e9b1fa7a --- /dev/null +++ b/guide/src/zh-CN/debugging.md @@ -0,0 +1,5 @@ +# 调试 + +**翻译即将到来。请参阅英文版本:(https://pyo3.rs/main/debugging.html)** + + \ No newline at end of file diff --git a/guide/src/zh-CN/ecosystem.md b/guide/src/zh-CN/ecosystem.md new file mode 100644 index 00000000000..616c602f138 --- /dev/null +++ b/guide/src/zh-CN/ecosystem.md @@ -0,0 +1,5 @@ +# PyO3 生态系统 + +**翻译即将到来。请参阅英文版本:(http://pyo3.rs/main/ecosystem.html)** + + \ No newline at end of file diff --git a/guide/src/zh-CN/ecosystem/async-await.md b/guide/src/zh-CN/ecosystem/async-await.md new file mode 100644 index 00000000000..7ed2577db4d --- /dev/null +++ b/guide/src/zh-CN/ecosystem/async-await.md @@ -0,0 +1,5 @@ +# 使用 `async` 和 `await` + +**翻译即将到来。请参阅英文版本:(https://pyo3.rs/main/ecosystem/async-await.html)** + + \ No newline at end of file diff --git a/guide/src/zh-CN/ecosystem/logging.md b/guide/src/zh-CN/ecosystem/logging.md new file mode 100644 index 00000000000..83daa706d17 --- /dev/null +++ b/guide/src/zh-CN/ecosystem/logging.md @@ -0,0 +1,5 @@ +# 日志记录 + +**翻译即将到来。请参阅英文版本:(https://pyo3.rs/main/ecosystem/logging.html)** + + \ No newline at end of file diff --git a/guide/src/zh-CN/ecosystem/tracing.md b/guide/src/zh-CN/ecosystem/tracing.md new file mode 100644 index 00000000000..02a1ac156a0 --- /dev/null +++ b/guide/src/zh-CN/ecosystem/tracing.md @@ -0,0 +1,5 @@ +# 跟踪 + +**翻译即将到来。请参阅英文版本:(https://pyo3.rs/main/ecosystem/tracing.html)** + + \ No newline at end of file diff --git a/guide/src/zh-CN/exception.md b/guide/src/zh-CN/exception.md new file mode 100644 index 00000000000..056584674bf --- /dev/null +++ b/guide/src/zh-CN/exception.md @@ -0,0 +1,5 @@ +# Python 异常 + +**翻译即将到来。请参阅英文版本:(https://pyo3.rs/main/exception.html)** + + \ No newline at end of file diff --git a/guide/src/zh-CN/faq.md b/guide/src/zh-CN/faq.md new file mode 100644 index 00000000000..1bfa83b2206 --- /dev/null +++ b/guide/src/zh-CN/faq.md @@ -0,0 +1,5 @@ +# 常见问题和故障排除 + +**翻译即将到来。请参阅英文版本:(https://pyo3.rs/main/faq.html)** + + \ No newline at end of file diff --git a/guide/src/zh-CN/features.md b/guide/src/zh-CN/features.md new file mode 100644 index 00000000000..9706caf0aa0 --- /dev/null +++ b/guide/src/zh-CN/features.md @@ -0,0 +1,5 @@ +# 功能参考 + +**翻译即将到来。请参阅英文版本:(https://pyo3.rs/main/features.html)** + + \ No newline at end of file diff --git a/guide/src/zh-CN/free-threading.md b/guide/src/zh-CN/free-threading.md new file mode 100644 index 00000000000..9f4bcbb0dad --- /dev/null +++ b/guide/src/zh-CN/free-threading.md @@ -0,0 +1,5 @@ +# 支持自由线程的 CPython + +**翻译即将到来。请参阅英文版本:(https://pyo3.rs/main/free-threading.html)** + + \ No newline at end of file diff --git a/guide/src/zh-CN/function.md b/guide/src/zh-CN/function.md new file mode 100644 index 00000000000..ce740f62897 --- /dev/null +++ b/guide/src/zh-CN/function.md @@ -0,0 +1,5 @@ +# Python 函数 + +**翻译即将到来。请参阅英文版本:(https://pyo3.rs/main/function.html)** + + \ No newline at end of file diff --git a/guide/src/zh-CN/function/error-handling.md b/guide/src/zh-CN/function/error-handling.md new file mode 100644 index 00000000000..5011863b092 --- /dev/null +++ b/guide/src/zh-CN/function/error-handling.md @@ -0,0 +1,5 @@ +# 错误处理 + +**翻译即将到来。请参阅英文版本:(https://pyo3.rs/main/function/error-handling.html)** + + \ No newline at end of file diff --git a/guide/src/zh-CN/function/signature.md b/guide/src/zh-CN/function/signature.md new file mode 100644 index 00000000000..d27967759e7 --- /dev/null +++ b/guide/src/zh-CN/function/signature.md @@ -0,0 +1,5 @@ +# 函数签名 + +**翻译即将到来。请参阅英文版本:(https://pyo3.rs/main/function/signature.html)** + + \ No newline at end of file diff --git a/guide/src/zh-CN/getting-started.md b/guide/src/zh-CN/getting-started.md new file mode 100644 index 00000000000..4e450a9f6e9 --- /dev/null +++ b/guide/src/zh-CN/getting-started.md @@ -0,0 +1,178 @@ +# 安装 + +要开始使用PyO3,您需要三样东西:Rust工具链、Python环境以及构建方式。我们将在下面逐一介绍这些内容。 + +> 如果您想与PyO3维护者和其他PyO3用户交流,请考虑加入[PyO3 Discord服务器](https://discord.gg/33kcChzH7f)。我们很想了解您的入门体验,这样我们就可以让PyO3对每个人都尽可能易于访问! + +## Rust + +首先,确保您的系统上已安装Rust。如果您还没有安装,请尝试按照[这里](https://www.rust-lang.org/tools/install)的说明进行安装。PyO3在`stable`和`nightly`版本上都可以运行,所以您可以选择最适合您的版本。最低要求的Rust版本是1.74。 + +如果您可以运行`rustc --version`并且版本足够新,那就可以开始了! + +## Python + +要使用PyO3,您至少需要Python 3.7。虽然您可以简单地使用系统上的默认Python解释器,但建议使用虚拟环境。 + +## 虚拟环境 + +虽然您可以使用任何您喜欢的虚拟环境管理器,但我们特别推荐使用`pyenv`,特别是如果您想要为多个不同的Python版本进行开发或测试,这就是本指南中示例将使用的工具。`pyenv`的安装说明可以在[这里](https://github.com/pyenv/pyenv#a-getting-pyenv)找到。(注意:要获得`pyenv activate`和`pyenv virtualenv`命令,您还需要安装[`pyenv-virtualenv`](https://github.com/pyenv/pyenv-virtualenv)插件。[pyenv安装程序](https://github.com/pyenv/pyenv-installer#installation--update--uninstallation)将一起安装这两个工具。) + +保留使用`pyenv`安装时使用的源代码可能很有用,以便将来调试时可以查看原始源文件。这可以通过在`pyenv install`命令中传递`--keep`标志来完成。 + +例如: + +```bash +pyenv install 3.12 --keep +``` + +### 构建 + +有许多构建和Python包管理系统,比如[`setuptools-rust`](https://github.com/PyO3/setuptools-rust)或[手动](./building-and-distribution.md)构建。我们推荐使用`maturin`,您可以在[这里](https://maturin.rs/installation.html)安装它。它是专门为与PyO3配合使用而开发的,提供了最"开箱即用"的体验,特别是如果您的目标是发布到PyPI。`maturin`只是一个Python包,所以您可以用安装其他Python包的相同方式来添加它。 + +系统Python: +```bash +pip install maturin --user +``` + +pipx: +```bash +pipx install maturin +``` + +pyenv: +```bash +pyenv activate pyo3 +pip install maturin +``` + +poetry: +```bash +poetry add -G dev maturin +``` + +安装后,您可以运行`maturin --version`来检查是否正确安装了它。 + +# 创建新项目 + +首先,您应该创建将包含新项目的文件夹和虚拟环境。这里我们将使用推荐的`pyenv`: + +```bash +mkdir pyo3-example +cd pyo3-example +pyenv virtualenv pyo3 +pyenv local pyo3 +``` + +之后,您应该安装构建管理器。在这个示例中,我们将使用`maturin`。激活虚拟环境后,将`maturin`添加到其中: + +```bash +pip install maturin +``` + +现在您可以初始化新项目: + +```bash +maturin init +``` + +如果`maturin`已经安装,您也可以直接使用它来创建新项目: + +```bash +maturin new -b pyo3 pyo3-example +cd pyo3-example +pyenv virtualenv pyo3 +pyenv local pyo3 +``` + +# 添加到现有项目 + +遗憾的是,`maturin`目前无法在现有项目中运行,所以如果您想在现有项目中使用Python,基本上有两个选择: + +1. 如上所述创建一个新项目,并将现有代码移动到该项目中 +2. 根据需要手动编辑项目配置 +> ℹ️ **译者注:maturin 在现有项目中存在限制的原因** +> - **配置冲突**:现有 `Cargo.toml` 可能缺少必要设置(如 `crate-type = ["cdylib"]`)或存在与 Python 绑定冲突的配置(如 `bin` 类型) +> - **设计目标**:`maturin` 专注于快速创建独立混合项目(预置 Rust + Python 绑定结构),而非改造现有代码 + +如果您选择第二个选项,以下是您需要注意的事项: + +## Cargo.toml + +确保您想要从Python访问的Rust crate被编译为库。您也可以有二进制输出,但您想要从Python访问的代码必须在库部分。此外,确保crate类型是`cdylib`,并如下添加PyO3作为依赖项: + +```toml +# 如果您在`Cargo.toml`中已经有[package]信息,可以忽略此部分! +[package] +# 这里的`name`是包的名称。 +name = "pyo3_start" +# 这些是良好的默认值: +version = "0.1.0" +edition = "2021" + +[lib] +# 原生库的名称。这是在Python中导入库时将使用的名称(如`import string_sum`)。 +# 如果您更改了这个,还必须更改`src/lib.rs`中`#[pymodule]`的名称。 +name = "pyo3_example" + +# "cdylib"是生成Python可以导入的共享库所必需的。 +crate-type = ["cdylib"] + +[dependencies] +pyo3 = { {{#PYO3_CRATE_VERSION}}, features = ["extension-module"] } +``` + +## pyproject.toml + +您还应该创建一个包含以下内容的`pyproject.toml`: + +```toml +[build-system] +requires = ["maturin>=1,<2"] +build-backend = "maturin" + +[project] +name = "pyo3_example" +requires-python = ">=3.7" +classifiers = [ + "Programming Language :: Rust", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", +] +``` + +## 运行代码 + +之后,您可以设置Rust代码在Python中可用,如下所示;例如,您可以将此代码放在`src/lib.rs`中: + +```rust,no_run +/// 用Rust实现的Python模块。此函数的名称必须与 +/// `Cargo.toml`中的`lib.name`设置匹配,否则Python将无法导入模块。 +#[pyo3::pymodule] +mod pyo3_example { + use pyo3::prelude::*; + + /// 将两个数字的和格式化为字符串。 + #[pyfunction] + fn sum_as_string(a: usize, b: usize) -> PyResult { + Ok((a + b).to_string()) + } +} +``` + +现在您可以运行`maturin develop`来准备Python包,之后您可以像这样使用它: + +```bash +$ maturin develop +# 当maturin运行编译时会有大量进度输出... +$ python +>>> import pyo3_example +>>> pyo3_example.sum_as_string(5, 20) +'25' +``` + +有关如何在Rust中使用Python代码的更多说明,请参见[Python from Rust](python-from-rust.md)页面。 + +## Maturin导入钩子 + +在开发过程中,代码中的任何更改都需要在测试之前运行`maturin develop`。为了简化开发过程,您可能想要安装[Maturin Import Hook](https://github.com/PyO3/maturin-import-hook),它会在导入有代码更改的库时自动运行`maturin develop`。 diff --git a/guide/src/zh-CN/index.md b/guide/src/zh-CN/index.md new file mode 100644 index 00000000000..0f2e696999a --- /dev/null +++ b/guide/src/zh-CN/index.md @@ -0,0 +1,295 @@ +# PyO3 用户指南 + +欢迎阅读 PyO3 用户指南!本书是 [PyO3 API 文档](https://docs.rs/pyo3) 的补充说明。它通过详实的示例与解析,系统阐述 PyO3 的全场景应用方案。 + +本指南按以下逻辑顺序展开: + 1. 入门指南 + 2. 封装 Rust 代码供Python调用 + 3. 在 Rust 中调用 Python 代码 + 4. 高级概念详解 + +请从左侧章节中选择跳转到个别主题,或继续下方以开始 PyO3 的 README。 + +
+ +# PyO3 + +[![actions status](https://img.shields.io/github/actions/workflow/status/PyO3/pyo3/ci.yml?branch=main&logo=github&style=)](https://github.com/PyO3/pyo3/actions) +[![benchmark](https://img.shields.io/endpoint?url=https://codspeed.io/badge.json)](https://codspeed.io/PyO3/pyo3) +[![codecov](https://img.shields.io/codecov/c/gh/PyO3/pyo3?logo=codecov)](https://codecov.io/gh/PyO3/pyo3) +[![crates.io](https://img.shields.io/crates/v/pyo3?logo=rust)](https://crates.io/crates/pyo3) +[![minimum rustc 1.63](https://img.shields.io/badge/rustc-1.63+-blue?logo=rust)](https://rust-lang.github.io/rfcs/2495-min-rust-version.html) +[![discord server](https://img.shields.io/discord/1209263839632424990?logo=discord)](https://discord.gg/33kcChzH7f) +[![contributing notes](https://img.shields.io/badge/contribute-on%20github-Green?logo=github)](https://github.com/PyO3/pyo3/blob/main/Contributing.md) + +PyO3 是 [Rust](https://www.rust-lang.org/) 与 [Python](https://www.python.org/) 的绑定库,提供创建原生 Python 扩展模块的工具,并支持在 Rust 二进制程序中运行和交互 Python 代码。 + +- 用户指南:[stable](https://pyo3.rs) | [main](https://pyo3.rs/main) + +- API 文档:[stable](https://docs.rs/pyo3/) | [main](https://pyo3.rs/main/doc/pyo3/) + +## 使用 + +需要 Rust 1.74 或更高版本。 + +PyO3 支持以下 Python 版本: + - CPython 3.7 或更高版本 + - PyPy 7.3(Python 3.9+) + - GraalPy 24.2 或更高版本(Python 3.11+) + +您可以使用 PyO3 在 Rust 中编写原生 Python 模块,或者在 Rust 二进制文件中嵌入 Python。以下部分依次解释这些内容。 + +### 从 Python 使用 Rust + +PyO3 可用于生成原生 Python 模块。首次尝试推荐使用 [`maturin`](https://github.com/PyO3/maturin)。`maturin` 通过最小化配置实现基于 Rust 的 Python 包的构建与发布。以下步骤安装 `maturin`,使用它生成并构建一个新的 Python 包,然后启动 Python 来导入并执行包中的函数。 + +首先,按照以下命令创建一个新目录,其中包含一个新的 Python `virtualenv`,并使用 Python 的包管理器 `pip` 将 `maturin` 安装到 virtualenv 中: + +```bash +# (将 `string_sum` 替换为所需的包名。) +$ mkdir string_sum +$ cd string_sum +$ python -m venv .env +$ source .env/bin/activate +$ pip install maturin +``` + +仍然在这个 `string_sum` 目录中,现在运行 `maturin init`。这将生成新的包源代码。在选择要使用的绑定时,选择 pyo3 绑定: + +```bash +$ maturin init +✔ 🤷 What kind of bindings to use? · pyo3 + ✨ Done! New project created string_sum +``` + +此命令生成的最重要文件是 `Cargo.toml` 和 `lib.rs`,大致如下所示: + +**`Cargo.toml`** + +```toml +[package] +name = "string_sum" +version = "0.1.0" +edition = "2021" + +[lib] +# 原生库的名称。这是 Python 中用于导入库的名称 +# (即 `import string_sum`)。如果您更改此名称,您还必须更改 +# `src/lib.rs` 中 `#[pymodule]` 的名称。 +name = "string_sum" +# "cdylib" 是生成供 Python 导入的共享库所必需的。 +# +# 下游 Rust 代码(包括 `bin/`、`examples/` 和 `tests/` 中的代码)将无法 +# `use string_sum;` 除非包括 "rlib" 或 "lib" crate 类型,例如: +# crate-type = ["cdylib", "rlib"] +crate-type = ["cdylib"] + +[dependencies] +pyo3 = { version = "0.25.1", features = ["extension-module"] } +``` + +**`src/lib.rs`** + +```rust +use pyo3::prelude::*; + +/// 将两个数字的和格式化为字符串。 +#[pyfunction] +fn sum_as_string(a: usize, b: usize) -> PyResult { + Ok((a + b).to_string()) +} + +/// 一个用 Rust 实现的 Python 模块。此函数的名称必须与 +/// `Cargo.toml` 中的 `lib.name` 设置匹配, +/// 否则 Python 将无法导入该模块。 +#[pymodule] +fn string_sum(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_function(wrap_pyfunction!(sum_as_string, m)?)?; + Ok(()) +} +``` + +最后,运行 `maturin develop`。这将构建包并将其安装到先前创建并激活的 Python virtualenv 中。然后,该包即可在 `python` 使用: + +```bash +$ maturin develop +# lots of progress output as maturin runs the compilation... +$ python +>>> import string_sum +>>> string_sum.sum_as_string(5, 20) +'25' +``` + +要对包进行更改,只需编辑 Rust 源代码,然后重新运行 `maturin develop` 以重新编译。 + +要将其作为单个复制粘贴运行,请使用下面的 bash 脚本(用所需的包名称替换第一个命令中的 `string_sum`): + +```bash +mkdir string_sum && cd "$_" +python -m venv .env +source .env/bin/activate +pip install maturin +maturin init --bindings pyo3 +maturin develop +``` + +如果您想能够运行 `cargo test` 或在 Cargo 工作区中使用此项目,并遇到链接器问题,请参阅 [FAQ](https://pyo3.rs/latest/faq.html#i-cant-run-cargo-test-or-i-cant-build-in-a-cargo-workspace-im-having-linker-issues-like-symbol-not-found-or-undefined-reference-to-_pyexc_systemerror) 中的一些解决方法。 + +除了使用 `maturin`,还可以使用 [`setuptools-rust`](https://github.com/PyO3/setuptools-rust) 或 [手动](https://pyo3.rs/latest/building-and-distribution.html#manual-builds) 构建。两者比 `maturin` 提供更多灵活性,但需要更多配置才能入门。 + +### 从 Rust 使用 Python + +要将 Python 嵌入到 Rust 二进制文件中,您需要确保 Python 安装包含共享库。以下步骤演示如何确保这一点(针对 Ubuntu),然后给出一些示例代码,用于运行嵌入式 Python 解释器。 + +要在 Ubuntu 上安装 Python 共享库: + +```bash +sudo apt install python3-dev +``` + +要在基于 RPM 的发行版(例如 Fedora、Red Hat、SuSE)上安装 Python 共享库,请安装 `python3-devel` 包。 + +使用 `cargo new` 启动一个新项目,并将 `pyo3` 添加到 `Cargo.toml` 中,如下所示: + +```toml +[dependencies.pyo3] +version = "0.25.1" +features = ["auto-initialize"] +``` + +示例程序显示 `sys.version` 的值和当前用户名: + +```rust +use pyo3::prelude::*; +use pyo3::types::IntoPyDict; +use pyo3::ffi::c_str; + +fn main() -> PyResult<()> { + Python::attach(|py| { + let sys = py.import("sys")?; + let version: String = sys.getattr("version")?.extract()?; + + let locals = [("os", py.import("os")?)].into_py_dict(py)?; + let code = c_str!("os.getenv('USER') or os.getenv('USERNAME') or 'Unknown'"); + let user: String = py.eval(code, None, Some(&locals))?.extract()?; + + println!("Hello {}, I'm Python {}", user, version); + Ok(()) + }) +} +``` + +指南有一个[部分](https://pyo3.rs/latest/python-from-rust.html),包含大量关于此主题的示例。 + +## 工具和库 + +- [maturin](https://github.com/PyO3/maturin) _构建和发布带有 pyo3、rust-cpython 或 cffi 绑定的 crate,以及作为 Python 包的 Rust 二进制文件_ +- [setuptools-rust](https://github.com/PyO3/setuptools-rust) _用于 Rust 支持的 Setuptools 插件_。 +- [pyo3-built](https://github.com/PyO3/pyo3-built) _简单的宏,用于将使用 [`built`](https://crates.io/crates/built) crate 获取的元数据公开为 [`PyDict`](https://docs.rs/pyo3/*/pyo3/types/struct.PyDict.html)_ +- [rust-numpy](https://github.com/PyO3/rust-numpy) _NumPy C-API 的 Rust 绑定_ +- [dict-derive](https://github.com/gperinazzo/dict-derive) _派生 FromPyObject 以自动将 Python 字典转换为 Rust 结构体_ +- [pyo3-log](https://github.com/vorner/pyo3-log) _从 Rust 到 Python 日志的桥梁_ +- [pythonize](https://github.com/davidhewitt/pythonize) _用于将 Rust 对象转换为 JSON 兼容 Python 对象的 Serde 序列化器_ +- [pyo3-async-runtimes](https://github.com/PyO3/pyo3-async-runtimes) _用于与 Python 的 Asyncio 库和 Rust 的异步运行时进行互操作的实用工具。_ +- [rustimport](https://github.com/mityax/rustimport) _直接从 Python 导入 Rust 文件或 crate,无需手动编译步骤。默认提供 pyo3 集成,并自动生成 pyo3 绑定代码。_ +- [pyo3-arrow](https://crates.io/crates/pyo3-arrow) _用于 pyo3 的轻量级 [Apache Arrow](https://arrow.apache.org/) 集成。_ +- [pyo3-bytes](https://crates.io/crates/pyo3-bytes) _[`bytes`](https://crates.io/crates/bytes) 和 pyo3 之间的集成。_ +- [pyo3-object_store](https://github.com/developmentseed/obstore/tree/main/pyo3-object_store) _[`object_store`](https://docs.rs/object_store) 和 [`pyo3`](https://github.com/PyO3/pyo3) 之间的集成。_ + +## 示例 + +- [arro3](https://github.com/kylebarron/arro3) _Apache Arrow 的最小 Python 库,连接到 Rust arrow crate。_ + - [arro3-compute](https://github.com/kylebarron/arro3/tree/main/arro3-compute) _`arro3-compute`_ + - [arro3-core](https://github.com/kylebarron/arro3/tree/main/arro3-core) _`arro3-core`_ + - [arro3-io](https://github.com/kylebarron/arro3/tree/main/arro3-io) _`arro3-io`_ +- [bed-reader](https://github.com/fastlmm/bed-reader) _简单高效地读取和写入 PLINK BED 格式。_ + - 显示 Rayon/ndarray::parallel(包括捕获错误、控制线程数)、Python 类型到 Rust 泛型、GitHub Actions +- [blake3-py](https://github.com/oconnor663/blake3-py) _[BLAKE3](https://github.com/BLAKE3-team/BLAKE3) 加密哈希函数的 Python 绑定。_ + - 在 GitHub Actions 上并行化[构建](https://github.com/oconnor663/blake3-py/blob/master/.github/workflows/dists.yml),针对 MacOS、Linux、Windows,包括无线程的 3.13t wheel。 +- [cellular_raza](https://cellular-raza.com) _一个基于细胞代理的模拟框架,用于从零开始构建复杂模型。_ +- [connector-x](https://github.com/sfu-db/connector-x/tree/main/connectorx-python) _最快的库,用于从 DB 加载数据到 Rust 和 Python 中的 DataFrame。_ +- [cryptography](https://github.com/pyca/cryptography/tree/main/src/rust) _Python 加密库,其中一些功能用 Rust 实现。_ +- [css-inline](https://github.com/Stranger6667/css-inline/tree/master/bindings/python) _用 Rust 实现的 Python CSS 内联。_ +- [datafusion-python](https://github.com/apache/arrow-datafusion-python) _一个绑定到 Apache Arrow 内存查询引擎 DataFusion 的 Python 库。_ +- [deltalake-python](https://github.com/delta-io/delta-rs/tree/main/python) _基于 delta-rs 的原生 Delta Lake Python 绑定,带有 Pandas 集成。_ +- [fastbloom](https://github.com/yankun1992/fastbloom) _一个快速的 [bloom filter](https://github.com/yankun1992/fastbloom#BloomFilter) | [counting bloom filter](https://github.com/yankun1992/fastbloom#countingbloomfilter),用 Rust 为 Rust 和 Python 实现!_ +- [fastuuid](https://github.com/thedrow/fastuuid/) _Rust 的 UUID 库的 Python 绑定。_ +- [feos](https://github.com/feos-org/feos) _Rust 中闪电般快速的热力学建模,带有完全开发的 Python 接口。_ +- [finalytics](https://github.com/Nnamdi-sys/finalytics) _Rust | Python 中的投资分析库。_ +- [forust](https://github.com/jinlow/forust) _用 Rust 编写的一个轻量级梯度提升决策树库。_ +- [geo-index](https://github.com/kylebarron/geo-index) _一个 Rust crate 和 [Python 库](https://github.com/kylebarron/geo-index/tree/main/python),用于打包的、不可变的、零拷贝空间索引。_ +- [granian](https://github.com/emmett-framework/granian) _一个用于 Python 应用程序的 Rust HTTP 服务器。_ +- [haem](https://github.com/BooleanCat/haem) _一个用于处理生物信息学问题的 Python 库。_ +- [html2text-rs](https://github.com/deedy5/html2text_rs) _将 HTML 转换为标记或纯文本的 Python 库。_ +- [html-py-ever](https://github.com/PyO3/setuptools-rust/tree/main/examples/html-py-ever) _通过 [kuchiki](https://github.com/kuchiki-rs/kuchiki) 使用 [html5ever](https://github.com/servo/html5ever) 来加速 HTML 解析和 CSS 选择。_ +- [hudi-rs](https://github.com/apache/hudi-rs) _Apache Hudi 的原生 Rust 实现,带有 C++ 和 Python API 绑定。_ +- [inline-python](https://github.com/m-ou-se/inline-python) _直接在 Rust 代码中内联 Python 代码。_ +- [johnnycanencrypt](https://github.com/kushaldas/johnnycanencrypt) 带有 Yubikey 支持的 OpenPGP 库。 +- [jsonschema](https://github.com/Stranger6667/jsonschema/tree/master/crates/jsonschema-py) _一个用于 Python 的高性能 JSON Schema 验证器。_ +- [mocpy](https://github.com/cds-astro/mocpy) _天文学 Python 库,提供数据结构,用于描述单位球面上的任意覆盖区域。_ +- [obstore](https://github.com/developmentseed/obstore) _最简单的、最高吞吐量的 Python 接口,用于 Amazon S3、Google Cloud Storage、Azure Storage 和其他 S3 兼容 API,由 Rust 驱动。_ +- [opendal](https://github.com/apache/opendal/tree/main/bindings/python) _一个数据访问层,允许用户以统一的方式轻松高效地从各种存储服务中检索数据。_ +- [orjson](https://github.com/ijl/orjson) _快速的 Python JSON 库。_ +- [ormsgpack](https://github.com/aviramha/ormsgpack) _快速的 Python msgpack 库。_ +- [polars](https://github.com/pola-rs/polars) _用 Rust | Python | Node.js 实现的快速多线程 DataFrame 库。_ +- [pycrdt](https://github.com/jupyter-server/pycrdt) _Rust CRDT 实现 [Yrs](https://github.com/y-crdt/y-crdt) 的 Python 绑定。_ +- [pydantic-core](https://github.com/pydantic/pydantic-core) _用 Rust 编写 pydantic 的核心验证逻辑。_ +- [primp](https://github.com/deedy5/primp) _最快的 Python HTTP 客户端,能够通过模仿其头部和 TLS/JA3/JA4/HTTP2 指纹来伪装 Web 浏览器。_ +- [rateslib](https://github.com/attack68/rateslib) _一个使用 Rust 扩展的 Python 固定收益库。_ +- [river](https://github.com/online-ml/river) _Python 中的在线机器学习,计算密集型统计算法用 Rust 实现。_ +- [robyn](https://github.com/sparckles/Robyn) 一个具有 Rust 运行时的超级快速异步 Python Web 框架。 +- [rust-python-coverage](https://github.com/cjermain/rust-python-coverage) _带有 Rust 和 Python 自动测试覆盖率的 PyO3 项目示例。_ +- [rnet](https://github.com/0x676e67/rnet) 带有黑魔法的异步 Python HTTP 客户端 +- [sail](https://github.com/lakehq/sail) _统一流、批处理和 AI 工作负载,兼容 Apache Spark。_ +- [tiktoken](https://github.com/openai/tiktoken) _一个用于 OpenAI 模型的快速 BPE 分词器。_ +- [tokenizers](https://github.com/huggingface/tokenizers/tree/main/bindings/python) _用 Rust 编写的 Hugging Face 分词器(NLP)的 Python 绑定。_ +- [tzfpy](http://github.com/ringsaturn/tzfpy) _一个快速将经度/纬度转换为时区名称的包。_ +- [utiles](https://github.com/jessekrubin/utiles) _快速的 Python Web 地图图块实用工具_ + +## 文章和其他媒体 + +- [(视频) 对比在 Free-Threaded 与常规 Python 3.13 中使用 Rust](https://www.youtube.com/watch?v=J7phN_M4GLM) - 2025 年 6 月 4 日 +- [(视频) 在 Python 中探索 Rust 五年所学的技巧](https://www.youtube.com/watch?v=KTQn_PTHNCw) - 2025 年 2 月 26 日 +- [(播客) 连接 Python 与 Rust:对 PyO3 维护者 David Hewitt 的访谈](https://www.youtube.com/watch?v=P47JUMSQagU) - 2024 年 8 月 30 日 +- [(视频) 从 Python 到 Rust,再回到 Python](https://www.youtube.com/watch?v=UmL_CA-v3O8) - 2024 年 7 月 3 日 +- [使用 Rust 将 Python AST 解析速度提高 20 倍](https://www.gauge.sh/blog/parsing-python-asts-20x-faster-with-rust) - 2024 年 6 月 17 日 +- [(视频) Python 如何通过 PyO3 利用 Rust](https://www.youtube.com/watch?v=UilujdubqVU) - 2024 年 5 月 18 日 +- [(视频) 将 Rust 和 Python 结合:两全其美?](https://www.youtube.com/watch?v=lyG6AKzu4ew) - 2024 年 3 月 1 日 +- [(视频) 使用 PyO3 扩展 Python 与 Rust](https://www.youtube.com/watch?v=T45ZEmSR1-s) - 2023 年 12 月 16 日 +- [PyO3 + rust-numpy 的一周(如何将数据管道速度提高 X 倍)](https://terencezl.github.io/blog/2023/06/06/a-week-of-pyo3-rust-numpy/) - 2023 年 6 月 6 日 +- [(播客) 与 David Hewitt 谈论 PyO3](https://rustacean-station.org/episode/david-hewitt/) - 2023 年 5 月 19 日 +- [使用不到 100 行 Rust 让 Python 快 100 倍](https://ohadravid.github.io/posts/2023-03-rusty-python/) - 2023 年 3 月 28 日 +- [Pydantic V2 如何利用 Rust 的超级能力](https://fosdem.org/2023/schedule/event/rust_how_pydantic_v2_leverages_rusts_superpowers/) - 2023 年 2 月 4 日 +- [我们如何使用 PyO3 用 Rust 扩展 River 统计模块](https://boring-guy.sh/posts/river-rust/) - 2022 年 12 月 23 日 +- [编写 Rust 中的 Python 扩展的九条规则](https://towardsdatascience.com/nine-rules-for-writing-python-extensions-in-rust-d35ea3a4ec29?sk=f8d808d5f414154fdb811e4137011437) - 2021 年 12 月 31 日 +- [使用 PyO3 从 Python 调用 Rust](https://saidvandeklundert.net/learn/2021-11-18-calling-rust-from-python-using-pyo3/) - 2021 年 11 月 18 日 +- [davidhewitt 在 2021 年 Rust Manchester 聚会的演讲](https://www.youtube.com/watch?v=-XyWG_klSAw&t=320s) - 2021 年 8 月 19 日 +- [逐步将小型 Python 项目移植到 Rust](https://blog.waleedkhan.name/port-python-to-rust/) - 2021 年 4 月 29 日 +- [Vortexa - 将 Rust 集成到 Python](https://www.vortexa.com/blog/integrating-rust-into-python) - 2021 年 4 月 12 日 +- [编写并发布 Rust 中的 Python 模块](https://blog.yossarian.net/2020/08/02/Writing-and-publishing-a-python-module-in-rust) - 2020 年 8 月 2 日 + +## 贡献 + +欢迎每个人为 PyO3 做出贡献!有许多方式来支持该项目,例如: + +- 在 GitHub 和 [Discord](https://discord.gg/33kcChzH7f) 上帮助 PyO3 用户解决问题 +- 改进文档 +- 编写功能和 bug 修复 +- 发布关于如何使用 PyO3 的博客和示例 + +如果您希望为 PyO3 贡献时间并寻找从哪里开始,我们的[贡献指南](contributing.md)和[架构指南](https://github.com/PyO3/pyo3/blob/main/Architecture.md)提供了更多资源。 + +如果您没有时间亲自贡献,但仍希望支持项目的未来成功,我们的一些维护者有 GitHub 赞助页面: + +- [davidhewitt](https://github.com/sponsors/davidhewitt) +- [messense](https://github.com/sponsors/messense) + +## 许可 + +PyO3 根据 [Apache-2.0 许可] 或 [MIT 许可] 许可,由您选择。 + +Python 根据 [Python 许可](https://docs.python.org/3/license.html) 许可。 + +除非您明确声明, 否则您有意提交以包含在 PyO3 中的任何贡献,如 Apache 许可中定义,将如上所述双重许可,而无任何附加条款或条件。 + + Deploys by Netlify \ No newline at end of file diff --git a/guide/src/zh-CN/migration.md b/guide/src/zh-CN/migration.md new file mode 100644 index 00000000000..0dc1af99632 --- /dev/null +++ b/guide/src/zh-CN/migration.md @@ -0,0 +1,5 @@ +# 附录 A: 迁移指南 + +**翻译即将到来。请参阅英文版本:(https://pyo3.rs/main/migration.html)** + + \ No newline at end of file diff --git a/guide/src/zh-CN/module.md b/guide/src/zh-CN/module.md new file mode 100644 index 00000000000..37b054a3d40 --- /dev/null +++ b/guide/src/zh-CN/module.md @@ -0,0 +1,174 @@ +# Python 模块 + +你可以使用 `#[pymodule]` 创建一个模块: + +```rust +use pyo3::prelude::*; + +#[pyfunction] +fn double(x: usize) -> usize { + x * 2 +} + +/// 这个模块是用 Rust 实现的。 +#[pymodule] +fn my_extension(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_function(wrap_pyfunction!(double, m)?) +} +``` + +`#[pymodule]` 过程宏负责将模块的初始化函数导出到 Python。 + +模块的名称默认为 Rust 函数的名称。你可以使用 `#[pyo3(name = "custom_name")]` 来覆盖模块名称: + +```rust +use pyo3::prelude::*; + +#[pyfunction] +fn double(x: usize) -> usize { + x * 2 +} + +#[pymodule(name = "custom_name")] +fn my_extension(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_function(wrap_pyfunction!(double, m)?) +} +``` + +模块的名称必须与 `.so` 或 `.pyd` 文件的名称匹配。否则,在 Python 中导入时会收到导入错误,消息为:`ImportError: dynamic module does not define module export function (PyInit_name_of_your_module)` 即 `ImportError: 动态模块未定义模块导出函数 (PyInit_你的模块名称)` + +要导入模块,可以: + - 如[手动构建](building-and-distribution.md)中所述复制共享库,或者 + - 使用工具,例如使用 [maturin](https://github.com/PyO3/maturin) 的 `maturin develop` 或使用 [setuptools-rust](https://github.com/PyO3/setuptools-rust) 的 `python setup.py develop`。 + +## 文档 + +模块初始化函数的 [Rust 文档注释](https://doc.rust-lang.org/stable/book/ch03-04-comments.html) 将自动应用于模块的 Python 文档字符串。 + +例如,基于上面的代码,这将打印 `这个模块是用 Rust 实现的。`: + +```python +import my_extension + +print(my_extension.__doc__) +``` + +## Python 子模块 + +你可以使用 [`Bound<'_, PyModule>::add_submodule()`]({{#PYO3_DOCS_URL}}/pyo3/prelude/trait.PyModuleMethods.html#tymethod.add_submodule) 在单个扩展模块中创建模块层次结构。 +例如,你可以定义模块 `parent_module` 和 `parent_module.child_module`。 + +```rust +use pyo3::prelude::*; + +#[pymodule] +fn parent_module(m: &Bound<'_, PyModule>) -> PyResult<()> { + register_child_module(m)?; + Ok(()) +} + +fn register_child_module(parent_module: &Bound<'_, PyModule>) -> PyResult<()> { + let child_module = PyModule::new(parent_module.py(), "child_module")?; + child_module.add_function(wrap_pyfunction!(func, &child_module)?)?; + parent_module.add_submodule(&child_module) +} + +#[pyfunction] +fn func() -> String { + "func".to_string() +} + +# Python::attach(|py| { +# use pyo3::wrap_pymodule; +# use pyo3::types::IntoPyDict; +# use pyo3::ffi::c_str; +# let parent_module = wrap_pymodule!(parent_module)(py); +# let ctx = [("parent_module", parent_module)].into_py_dict(py).unwrap(); +# +# py.run(c_str!("assert parent_module.child_module.func() == 'func'"), None, Some(&ctx)).unwrap(); +# }) +``` + +请注意,这并不定义一个包,因此不会允许 Python 代码通过 `from parent_module import child_module` 直接导入子模块。更多信息,请参阅 [#759](https://github.com/PyO3/pyo3/issues/759) 和 [#1517](https://github.com/PyO3/pyo3/issues/1517#issuecomment-808664021)。 + +对于嵌套模块,不需要在它们上添加 `#[pymodule]`,这仅在顶级模块上是必需的。 + +## 声明式模块 + +另一种基于 Rust 内联模块的语法也可用于声明模块。 + +例如: +```rust +# mod declarative_module_test { +use pyo3::prelude::*; + +#[pyfunction] +fn double(x: usize) -> usize { + x * 2 +} + +#[pymodule] +mod my_extension { + use super::*; + + #[pymodule_export] + use super::double; // 将 double 函数作为模块的一部分导出 + + #[pymodule_export] + const PI: f64 = std::f64::consts::PI; // 将 PI 常量作为模块的一部分导出 + + #[pyfunction] // 这将成为模块的一部分 + fn triple(x: usize) -> usize { + x * 3 + } + + #[pyclass] // 这将成为模块的一部分 + struct Unit; + + #[pymodule] + mod submodule { + // 这是一个子模块 + } + + #[pymodule_init] + fn init(m: &Bound<'_, PyModule>) -> PyResult<()> { + // 模块初始化时运行的任意代码 + m.add("double2", m.getattr("double")?) + } +} +# } +``` + +`#[pymodule]` 宏会自动将其中声明的 `#[pyclass]` 宏的 `module` 属性设置为其名称。 +对于嵌套模块,会自动添加父模块的名称。 +在以下示例中,`Unit` 类的 `module` 将为 `my_extension.submodule`,因为它是正确嵌套的, +但 `Ext` 类的 `module` 将为默认的 `builtins`,因为它未嵌套。 + +```rust +# mod declarative_module_module_attr_test { +use pyo3::prelude::*; + +#[pyclass] +struct Ext; + +#[pymodule] +mod my_extension { + use super::*; + + #[pymodule_export] + use super::Ext; + + #[pymodule] + mod submodule { + use super::*; + // 这是一个子模块 + + #[pyclass] // 这将成为模块的一部分 + struct Unit; + } +} +# } +``` +可以使用 `#[pyo3(module = "MY_MODULE")]` 选项自定义 `pymodule()` 的 `module` 值。 + +对于非顶级模块,可以向 `pymodule()` 提供 `submodule` 参数——对于嵌套在 `#[pymodule]` 中的模块,它会自动设置。 \ No newline at end of file diff --git a/guide/src/zh-CN/parallelism.md b/guide/src/zh-CN/parallelism.md new file mode 100644 index 00000000000..879330953a0 --- /dev/null +++ b/guide/src/zh-CN/parallelism.md @@ -0,0 +1,5 @@ +# 并行性 + +**翻译即将到来。请参阅英文版本:(https://pyo3.rs/main/parallelism.html)** + + \ No newline at end of file diff --git a/guide/src/zh-CN/performance.md b/guide/src/zh-CN/performance.md new file mode 100644 index 00000000000..96a9a7d77c1 --- /dev/null +++ b/guide/src/zh-CN/performance.md @@ -0,0 +1,5 @@ +# 性能 + +**翻译即将到来。请参阅英文版本:(https://pyo3.rs/main/performance.html)** + + \ No newline at end of file diff --git a/guide/src/zh-CN/python-from-rust.md b/guide/src/zh-CN/python-from-rust.md new file mode 100644 index 00000000000..383eae5df50 --- /dev/null +++ b/guide/src/zh-CN/python-from-rust.md @@ -0,0 +1,5 @@ +# 在 Rust 调用 Python + +**翻译即将到来。请参阅英文版本:(https://pyo3.rs/main/python-from-rust.html)** + + \ No newline at end of file diff --git a/guide/src/zh-CN/python-from-rust/calling-existing-code.md b/guide/src/zh-CN/python-from-rust/calling-existing-code.md new file mode 100644 index 00000000000..bc30992f307 --- /dev/null +++ b/guide/src/zh-CN/python-from-rust/calling-existing-code.md @@ -0,0 +1,5 @@ +# 执行现有的 Python 代码 + +**翻译即将到来。请参阅英文版本:(https://pyo3.rs/main/python-from-rust/calling-existing-code.html)** + + \ No newline at end of file diff --git a/guide/src/zh-CN/python-from-rust/function-calls.md b/guide/src/zh-CN/python-from-rust/function-calls.md new file mode 100644 index 00000000000..d247d93b6e6 --- /dev/null +++ b/guide/src/zh-CN/python-from-rust/function-calls.md @@ -0,0 +1,5 @@ +# 调用 Python 函数 + +**翻译即将到来。请参阅英文版本:(https://pyo3.rs/main/python-from-rust/function-calls.html)** + + \ No newline at end of file diff --git a/guide/src/zh-CN/python-typing-hints.md b/guide/src/zh-CN/python-typing-hints.md new file mode 100644 index 00000000000..149defb3762 --- /dev/null +++ b/guide/src/zh-CN/python-typing-hints.md @@ -0,0 +1,5 @@ +# 附录 C: Python 类型提示 + +**翻译即将到来。请参阅英文版本:(https://pyo3.rs/main/python-typing-hints.html)** + + \ No newline at end of file diff --git a/guide/src/zh-CN/rust-from-python.md b/guide/src/zh-CN/rust-from-python.md new file mode 100644 index 00000000000..c4404951ab3 --- /dev/null +++ b/guide/src/zh-CN/rust-from-python.md @@ -0,0 +1,13 @@ +# 在 Python 调用 Rust + +指南的本章旨在解释如何将 Rust 代码包装成 Python 对象。 + +PyO3 使用 Rust 的“过程宏”来提供一个强大而简单的 API,用于指定哪些 Rust 代码应该映射到 Python 对象。 + +PyO3 可以创建三种类型的 Python 对象: + +- Python 模块,通过 `#[pymodule]` 宏 +- Python 函数,通过 `#[pyfunction]` 宏 +- Python 类,通过 `#[pyclass]` 宏(加上 `#[pymethods]` 来定义这些类的方法) + +下面的子章节将依次介绍这些内容。 \ No newline at end of file diff --git a/guide/src/zh-CN/trait-bounds.md b/guide/src/zh-CN/trait-bounds.md new file mode 100644 index 00000000000..d9dbee1ef23 --- /dev/null +++ b/guide/src/zh-CN/trait-bounds.md @@ -0,0 +1,5 @@ +# 附录 B: Trait 边界 + +**翻译即将到来。请参阅英文版本:(https://pyo3.rs/main/trait-bounds.html)** + + \ No newline at end of file diff --git a/guide/src/zh-CN/type-stub.md b/guide/src/zh-CN/type-stub.md new file mode 100644 index 00000000000..03c1c580075 --- /dev/null +++ b/guide/src/zh-CN/type-stub.md @@ -0,0 +1,5 @@ +# 类型存根生成和内省 + +**翻译即将到来。请参阅英文版本:(https://pyo3.rs/main/type-stub.html)** + + \ No newline at end of file diff --git a/guide/src/zh-CN/types.md b/guide/src/zh-CN/types.md new file mode 100644 index 00000000000..35cfafd039f --- /dev/null +++ b/guide/src/zh-CN/types.md @@ -0,0 +1,5 @@ +# Python 对象类型 + +**翻译即将到来。请参阅英文版本:(https://pyo3.rs/main/types.html)** + + \ No newline at end of file diff --git a/guide/theme/index.hbs b/guide/theme/index.hbs new file mode 100644 index 00000000000..1abb4e7ce4f --- /dev/null +++ b/guide/theme/index.hbs @@ -0,0 +1,311 @@ + + + + + + {{ title }} - {{ book_title }} + {{#if is_print }} + + {{/if}} + + {{> head }} + + + + + + {{#if favicon_png }} + + {{/if}} + {{#if favicon_svg }} + + {{/if}} + + + + + {{#if print_enable }} + + {{/if}} + + + + {{#if copy_fonts }} + + {{/if}} + + + + + {{#if additional_css }} + {{# each additional_css }} + + {{/each}} + {{/if}} + + + {{#if additional_js }} + {{# each additional_js }} + + {{/each}} + {{/if}} + + + {{#if mathjax_support }} + + {{/if}} + + + + + + + + + + + +{{#if playground_line_numbers }} + +{{/if}} + + + + + + + + + +
+ +
+ {{> header }} + + + + {{#if search_enabled }} + + {{/if}} + + + + +
+
+ {{{ content }}} +
+ + +
+
+ + + +
+ +{{#if livereload }} + + +{{/if}} + +{{#if playground_copyable }} + + + + + +{{/if}} + +{{#if search_enabled }} + + + +{{/if}} + + + + + + +{{#if additional_js }} + {{# each additional_js }} + + {{/each}} +{{/if}} + +{{#if live_translations }} + +{{/if}} + + \ No newline at end of file diff --git a/guide/theme/tabs.js b/guide/theme/tabs.js index 8ba5e878c39..66876e05949 100644 --- a/guide/theme/tabs.js +++ b/guide/theme/tabs.js @@ -73,3 +73,10 @@ document.addEventListener('DOMContentLoaded', () => { } } }); + +// Language switcher function +// function switchLanguage(lang) { +// var currentPath = window.location.pathname; +// var newPath = currentPath.replace(/\/(en|cn)\//, '/' + lang + '/'); +// window.location.href = newPath; +// } \ No newline at end of file diff --git a/newsfragments/5282.added.md b/newsfragments/5282.added.md new file mode 100644 index 00000000000..6da3ed7f4ff --- /dev/null +++ b/newsfragments/5282.added.md @@ -0,0 +1 @@ +Added translation of PyO3 documentation to Chinese (zh-CN). \ No newline at end of file diff --git a/newsfragments/5324.changed.md b/newsfragments/5324.changed.md new file mode 100644 index 00000000000..11aed953100 --- /dev/null +++ b/newsfragments/5324.changed.md @@ -0,0 +1 @@ +Add fast-path to `PyTypeInfo::type_object` for `#[pyclass]` types. diff --git a/newsfragments/5325.changed.md b/newsfragments/5325.changed.md new file mode 100644 index 00000000000..aba0a81cc50 --- /dev/null +++ b/newsfragments/5325.changed.md @@ -0,0 +1 @@ +deprecated `PyObject` type alias for `Py` \ No newline at end of file diff --git a/pyo3-benches/benches/bench_pyclass.rs b/pyo3-benches/benches/bench_pyclass.rs index 7e026f74bd1..0967a897649 100644 --- a/pyo3-benches/benches/bench_pyclass.rs +++ b/pyo3-benches/benches/bench_pyclass.rs @@ -32,7 +32,7 @@ pub fn first_time_init(b: &mut Bencher<'_>) { // This is using an undocumented internal PyO3 API to measure pyclass performance; please // don't use this in your own code! let ty = LazyTypeObject::::new(); - ty.get_or_init(py); + ty.get_or_try_init(py).unwrap(); }); }); } diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index c21373f8ed9..3ff700074dc 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -1237,7 +1237,7 @@ fn impl_complex_enum_struct_variant_cls( complex_enum_variant_field_getter(&variant_cls_type, field_name, field.span, ctx)?; let field_getter_impl = quote! { - fn #field_name(slf: #pyo3_path::PyClassGuard<'_, Self>, py: #pyo3_path::Python<'_>) -> #pyo3_path::PyResult<#pyo3_path::PyObject> { + fn #field_name(slf: #pyo3_path::PyClassGuard<'_, Self>, py: #pyo3_path::Python<'_>) -> #pyo3_path::PyResult<#pyo3_path::Py<#pyo3_path::PyAny>> { #[allow(unused_imports)] use #pyo3_path::impl_::pyclass::Probe as _; match &*slf.into_super() { @@ -1313,7 +1313,7 @@ fn impl_complex_enum_tuple_variant_field_getters( }) .collect(); let field_getter_impl: syn::ImplItemFn = parse_quote! { - fn #field_name(slf: #pyo3_path::PyClassGuard<'_, Self>, py: #pyo3_path::Python<'_>) -> #pyo3_path::PyResult<#pyo3_path::PyObject> { + fn #field_name(slf: #pyo3_path::PyClassGuard<'_, Self>, py: #pyo3_path::Python<'_>) -> #pyo3_path::PyResult<#pyo3_path::Py<#pyo3_path::PyAny>> { #[allow(unused_imports)] use #pyo3_path::impl_::pyclass::Probe as _; match &*slf.into_super() { @@ -1374,7 +1374,7 @@ fn impl_complex_enum_tuple_variant_getitem( .collect(); let mut get_item_method_impl: syn::ImplItemFn = parse_quote! { - fn __getitem__(slf: #pyo3_path::PyClassGuard<'_, Self>, py: #pyo3_path::Python<'_>, idx: usize) -> #pyo3_path::PyResult< #pyo3_path::PyObject> { + fn __getitem__(slf: #pyo3_path::PyClassGuard<'_, Self>, py: #pyo3_path::Python<'_>, idx: usize) -> #pyo3_path::PyResult< #pyo3_path::Py<#pyo3_path::PyAny>> { match idx { #( #match_arms, )* _ => ::std::result::Result::Err(#pyo3_path::exceptions::PyIndexError::new_err("tuple index out of range")), @@ -1582,7 +1582,7 @@ pub fn gen_complex_enum_variant_attr( let variant_cls = format_ident!("{}_{}", cls, member); let associated_method = quote! { - fn #wrapper_ident(py: #pyo3_path::Python<'_>) -> #pyo3_path::PyResult<#pyo3_path::PyObject> { + fn #wrapper_ident(py: #pyo3_path::Python<'_>) -> #pyo3_path::PyResult<#pyo3_path::Py<#pyo3_path::PyAny>> { ::std::result::Result::Ok(py.get_type::<#variant_cls>().into_any().unbind()) } }; @@ -1845,7 +1845,12 @@ fn impl_pytypeinfo(cls: &syn::Ident, attr: &PyClassArgs, ctx: &Ctx) -> TokenStre fn type_object_raw(py: #pyo3_path::Python<'_>) -> *mut #pyo3_path::ffi::PyTypeObject { use #pyo3_path::prelude::PyTypeMethods; <#cls as #pyo3_path::impl_::pyclass::PyClassImpl>::lazy_type_object() - .get_or_init(py) + .get_or_try_init(py) + .unwrap_or_else(|e| #pyo3_path::impl_::pyclass::type_object_init_failed( + py, + e, + ::NAME + )) .as_type_ptr() } } @@ -1953,7 +1958,7 @@ fn pyclass_richcmp_simple_enum( py: #pyo3_path::Python, other: &#pyo3_path::Bound<'_, #pyo3_path::PyAny>, op: #pyo3_path::pyclass::CompareOp - ) -> #pyo3_path::PyResult<#pyo3_path::PyObject> { + ) -> #pyo3_path::PyResult<#pyo3_path::Py<#pyo3_path::PyAny>> { #eq #eq_int @@ -1987,7 +1992,7 @@ fn pyclass_richcmp( py: #pyo3_path::Python, other: &#pyo3_path::Bound<'_, #pyo3_path::PyAny>, op: #pyo3_path::pyclass::CompareOp - ) -> #pyo3_path::PyResult<#pyo3_path::PyObject> { + ) -> #pyo3_path::PyResult<#pyo3_path::Py<#pyo3_path::PyAny>> { let self_val = self; if let ::std::result::Result::Ok(other) = other.cast::() { let other = &*other.borrow(); diff --git a/pyo3-macros-backend/src/pyimpl.rs b/pyo3-macros-backend/src/pyimpl.rs index 388e5184bdf..282a6f35d7b 100644 --- a/pyo3-macros-backend/src/pyimpl.rs +++ b/pyo3-macros-backend/src/pyimpl.rs @@ -236,7 +236,7 @@ pub fn gen_py_const(cls: &syn::Type, spec: &ConstSpec, ctx: &Ctx) -> MethodAndMe let Ctx { pyo3_path, .. } = ctx; let associated_method = quote! { - fn #wrapper_ident(py: #pyo3_path::Python<'_>) -> #pyo3_path::PyResult<#pyo3_path::PyObject> { + fn #wrapper_ident(py: #pyo3_path::Python<'_>) -> #pyo3_path::PyResult<#pyo3_path::Py<#pyo3_path::PyAny>> { #pyo3_path::IntoPyObjectExt::into_py_any(#cls::#member, py) } }; diff --git a/pyo3-macros-backend/src/pymethod.rs b/pyo3-macros-backend/src/pymethod.rs index 6d7b455988f..eede90f882f 100644 --- a/pyo3-macros-backend/src/pymethod.rs +++ b/pyo3-macros-backend/src/pymethod.rs @@ -565,7 +565,7 @@ pub(crate) fn impl_py_class_attribute( let body = quotes::ok_wrap(fncall, ctx); let associated_method = quote! { - fn #wrapper_ident(py: #pyo3_path::Python<'_>) -> #pyo3_path::PyResult<#pyo3_path::PyObject> { + fn #wrapper_ident(py: #pyo3_path::Python<'_>) -> #pyo3_path::PyResult<#pyo3_path::Py<#pyo3_path::PyAny>> { let function = #cls::#name; // Shadow the method name to avoid #3017 let result = #body; #pyo3_path::impl_::wrap::converter(&result).map_into_pyobject(py, result) diff --git a/pytests/noxfile.py b/pytests/noxfile.py index f5a332c7a3c..91d9a7e9b43 100644 --- a/pytests/noxfile.py +++ b/pytests/noxfile.py @@ -34,3 +34,58 @@ def try_install_binary(package: str, constraint: str): def bench(session: nox.Session): session.install(".[dev]") session.run("pytest", "--benchmark-enable", "--benchmark-only", *session.posargs) + + +@nox.session +def build_guide(session: nox.Session): + """Build the mdBook guide for all languages""" + # Build main guide (English) + session.run("mdbook", "build", "guide", external=True) + # Build Chinese guide if it exists + try: + session.run("mdbook", "build", "guide/cn", external=True) + except CommandFailed: + print("Chinese guide build failed or doesn't exist, continuing...") + + +@nox.session +def check_guide(session: nox.Session): + """Build and check links in the mdBook guide""" + # Build all guides first + session.run("mdbook", "build", "guide", external=True) + try: + session.run("mdbook", "build", "guide/cn", external=True) + except CommandFailed: + print("Chinese guide build failed or doesn't exist, continuing...") + + # Run lychee link checker on the built output + session.run( + "lychee", + "--include-fragments", + "target/guide/", + "--remap", + "file://target/guide/=https://pyo3.rs/", + "--remap", + "file://target/guide/cn/=https://pyo3.rs/cn/", + "--accept=200,429", + "--exclude-path", + "target/guide/doc/", + external=True, + ) + + +@nox.session +def ruff(session: nox.Session): + """Check code formatting and linting with ruff""" + session.install("ruff") + + # Run ruff format check + session.run("ruff", "format", ".", "--check") + + # Run ruff linting + session.run("ruff", "check", ".") + """Build the complete Netlify site""" + session.install("requests", "towncrier") + + # Run the netlify build script + session.run("bash", ".netlify/build.sh", *session.posargs, external=True) diff --git a/pytests/src/awaitable.rs b/pytests/src/awaitable.rs index 01a93c70a0d..8481e2f6528 100644 --- a/pytests/src/awaitable.rs +++ b/pytests/src/awaitable.rs @@ -11,13 +11,13 @@ use pyo3::prelude::*; #[pyclass] #[derive(Debug)] pub(crate) struct IterAwaitable { - result: Option>, + result: Option>>, } #[pymethods] impl IterAwaitable { #[new] - fn new(result: PyObject) -> Self { + fn new(result: Py) -> Self { IterAwaitable { result: Some(Ok(result)), } @@ -31,7 +31,7 @@ impl IterAwaitable { pyself } - fn __next__(&mut self, py: Python<'_>) -> PyResult { + fn __next__(&mut self, py: Python<'_>) -> PyResult> { match self.result.take() { Some(res) => match res { Ok(v) => Err(PyStopIteration::new_err(v)), @@ -46,13 +46,13 @@ impl IterAwaitable { pub(crate) struct FutureAwaitable { #[pyo3(get, set, name = "_asyncio_future_blocking")] py_block: bool, - result: Option>, + result: Option>>, } #[pymethods] impl FutureAwaitable { #[new] - fn new(result: PyObject) -> Self { + fn new(result: Py) -> Self { FutureAwaitable { py_block: false, result: Some(Ok(result)), diff --git a/pytests/src/objstore.rs b/pytests/src/objstore.rs index 8e729052992..89643c3967d 100644 --- a/pytests/src/objstore.rs +++ b/pytests/src/objstore.rs @@ -3,7 +3,7 @@ use pyo3::prelude::*; #[pyclass] #[derive(Default)] pub struct ObjStore { - obj: Vec, + obj: Vec>, } #[pymethods] diff --git a/src/conversions/std/path.rs b/src/conversions/std/path.rs index 5a16aba1273..ef51367e03c 100644 --- a/src/conversions/std/path.rs +++ b/src/conversions/std/path.rs @@ -3,7 +3,7 @@ use crate::ffi_ptr_ext::FfiPtrExt; use crate::instance::Bound; use crate::sync::GILOnceCell; use crate::types::any::PyAnyMethods; -use crate::{ffi, FromPyObject, PyAny, PyErr, PyObject, PyResult, Python}; +use crate::{ffi, FromPyObject, Py, PyAny, PyErr, PyResult, Python}; use std::borrow::Cow; use std::ffi::OsString; use std::path::{Path, PathBuf}; @@ -25,7 +25,7 @@ impl<'py> IntoPyObject<'py> for &Path { #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { - static PY_PATH: GILOnceCell = GILOnceCell::new(); + static PY_PATH: GILOnceCell> = GILOnceCell::new(); PY_PATH .import(py, "pathlib", "Path")? .call((self.as_os_str(),), None) diff --git a/src/coroutine.rs b/src/coroutine.rs index 671defb1770..cf58b742f81 100644 --- a/src/coroutine.rs +++ b/src/coroutine.rs @@ -15,7 +15,7 @@ use crate::{ exceptions::{PyAttributeError, PyRuntimeError, PyStopIteration}, panic::PanicException, types::{string::PyStringMethods, PyIterator, PyString}, - Bound, IntoPyObject, IntoPyObjectExt, Py, PyAny, PyErr, PyObject, PyResult, Python, + Bound, IntoPyObject, IntoPyObjectExt, Py, PyAny, PyErr, PyResult, Python, }; pub(crate) mod cancel; @@ -31,7 +31,8 @@ pub struct Coroutine { name: Option>, qualname_prefix: Option<&'static str>, throw_callback: Option, - future: Option> + Send>>>, + #[allow(clippy::type_complexity)] + future: Option>> + Send>>>, waker: Option>, } @@ -71,7 +72,7 @@ impl Coroutine { } } - fn poll(&mut self, py: Python<'_>, throw: Option) -> PyResult { + fn poll(&mut self, py: Python<'_>, throw: Option>) -> PyResult> { // raise if the coroutine has already been run to completion let future_rs = match self.future { Some(ref mut fut) => fut, @@ -145,11 +146,11 @@ impl Coroutine { } } - fn send(&mut self, py: Python<'_>, _value: &Bound<'_, PyAny>) -> PyResult { + fn send(&mut self, py: Python<'_>, _value: &Bound<'_, PyAny>) -> PyResult> { self.poll(py, None) } - fn throw(&mut self, py: Python<'_>, exc: PyObject) -> PyResult { + fn throw(&mut self, py: Python<'_>, exc: Py) -> PyResult> { self.poll(py, Some(exc)) } @@ -163,7 +164,7 @@ impl Coroutine { self_ } - fn __next__(&mut self, py: Python<'_>) -> PyResult { + fn __next__(&mut self, py: Python<'_>) -> PyResult> { self.poll(py, None) } } diff --git a/src/coroutine/cancel.rs b/src/coroutine/cancel.rs index 47f5d69430a..49185fa56d7 100644 --- a/src/coroutine/cancel.rs +++ b/src/coroutine/cancel.rs @@ -1,4 +1,4 @@ -use crate::{Py, PyAny, PyObject}; +use crate::{Py, PyAny}; use std::future::Future; use std::pin::Pin; use std::sync::{Arc, Mutex}; @@ -6,7 +6,7 @@ use std::task::{Context, Poll, Waker}; #[derive(Debug, Default)] struct Inner { - exception: Option, + exception: Option>, waker: Option, } @@ -28,7 +28,7 @@ impl CancelHandle { } /// Poll to retrieve the exception thrown in the associated coroutine. - pub fn poll_cancelled(&mut self, cx: &mut Context<'_>) -> Poll { + pub fn poll_cancelled(&mut self, cx: &mut Context<'_>) -> Poll> { let mut inner = self.0.lock().unwrap(); if let Some(exc) = inner.exception.take() { return Poll::Ready(exc); @@ -43,7 +43,7 @@ impl CancelHandle { } /// Retrieve the exception thrown in the associated coroutine. - pub async fn cancelled(&mut self) -> PyObject { + pub async fn cancelled(&mut self) -> Py { Cancelled(self).await } @@ -57,7 +57,7 @@ impl CancelHandle { struct Cancelled<'a>(&'a mut CancelHandle); impl Future for Cancelled<'_> { - type Output = PyObject; + type Output = Py; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { self.0.poll_cancelled(cx) } diff --git a/src/coroutine/waker.rs b/src/coroutine/waker.rs index 9400d66c3d6..0afb024a9ab 100644 --- a/src/coroutine/waker.rs +++ b/src/coroutine/waker.rs @@ -1,7 +1,7 @@ use crate::sync::GILOnceCell; use crate::types::any::PyAnyMethods; use crate::types::PyCFunction; -use crate::{intern, wrap_pyfunction, Bound, Py, PyAny, PyObject, PyResult, Python}; +use crate::{intern, wrap_pyfunction, Bound, Py, PyAny, PyResult, Python}; use pyo3_macros::pyfunction; use std::sync::Arc; use std::task::Wake; @@ -52,13 +52,13 @@ impl Wake for AsyncioWaker { } struct LoopAndFuture { - event_loop: PyObject, - future: PyObject, + event_loop: Py, + future: Py, } impl LoopAndFuture { fn new(py: Python<'_>) -> PyResult { - static GET_RUNNING_LOOP: GILOnceCell = GILOnceCell::new(); + static GET_RUNNING_LOOP: GILOnceCell> = GILOnceCell::new(); let import = || -> PyResult<_> { let module = py.import("asyncio")?; Ok(module.getattr("get_running_loop")?.into()) diff --git a/src/err/err_state.rs b/src/err/err_state.rs index bb1fe1a465f..8616979310f 100644 --- a/src/err/err_state.rs +++ b/src/err/err_state.rs @@ -9,7 +9,7 @@ use crate::{ ffi, ffi_ptr_ext::FfiPtrExt, types::{PyAnyMethods, PyTraceback, PyType}, - Bound, Py, PyAny, PyErrArguments, PyObject, PyTypeInfo, Python, + Bound, Py, PyAny, PyErrArguments, PyTypeInfo, Python, }; pub(crate) struct PyErrState { @@ -263,8 +263,8 @@ impl PyErrStateNormalized { } pub(crate) struct PyErrStateLazyFnOutput { - pub(crate) ptype: PyObject, - pub(crate) pvalue: PyObject, + pub(crate) ptype: Py, + pub(crate) pvalue: Py, } pub(crate) type PyErrStateLazyFn = @@ -368,7 +368,7 @@ fn raise_lazy(py: Python<'_>, lazy: Box) { mod tests { use crate::{ - exceptions::PyValueError, sync::GILOnceCell, PyErr, PyErrArguments, PyObject, Python, + exceptions::PyValueError, sync::GILOnceCell, Py, PyAny, PyErr, PyErrArguments, Python, }; #[test] @@ -379,7 +379,7 @@ mod tests { struct RecursiveArgs; impl PyErrArguments for RecursiveArgs { - fn arguments(self, py: Python<'_>) -> PyObject { + fn arguments(self, py: Python<'_>) -> Py { // .value(py) triggers normalization ERR.get(py) .expect("is set just below") @@ -403,7 +403,7 @@ mod tests { struct GILSwitchArgs; impl PyErrArguments for GILSwitchArgs { - fn arguments(self, py: Python<'_>) -> PyObject { + fn arguments(self, py: Python<'_>) -> Py { // releasing the GIL potentially allows for other threads to deadlock // with the normalization going on here py.detach(|| { diff --git a/src/err/impls.rs b/src/err/impls.rs index d763ba93d80..6de043aabe0 100644 --- a/src/err/impls.rs +++ b/src/err/impls.rs @@ -1,5 +1,5 @@ -use crate::IntoPyObject; -use crate::{err::PyErrArguments, exceptions, PyErr, PyObject, Python}; +use crate::{err::PyErrArguments, exceptions, PyErr, Python}; +use crate::{IntoPyObject, Py, PyAny}; use std::io; /// Convert `PyErr` to `io::Error` @@ -77,7 +77,7 @@ impl From for PyErr { } impl PyErrArguments for io::Error { - fn arguments(self, py: Python<'_>) -> PyObject { + fn arguments(self, py: Python<'_>) -> Py { //FIXME(icxolu) remove unwrap self.to_string() .into_pyobject(py) @@ -94,7 +94,7 @@ impl From> for PyErr { } impl PyErrArguments for io::IntoInnerError { - fn arguments(self, py: Python<'_>) -> PyObject { + fn arguments(self, py: Python<'_>) -> Py { self.into_error().arguments(py) } } @@ -108,7 +108,7 @@ impl From for PyErr { macro_rules! impl_to_pyerr { ($err: ty, $pyexc: ty) => { impl PyErrArguments for $err { - fn arguments(self, py: Python<'_>) -> PyObject { + fn arguments(self, py: Python<'_>) -> $crate::Py<$crate::PyAny> { // FIXME(icxolu) remove unwrap self.to_string() .into_pyobject(py) diff --git a/src/err/mod.rs b/src/err/mod.rs index 9c48e30325e..16265865283 100644 --- a/src/err/mod.rs +++ b/src/err/mod.rs @@ -10,7 +10,7 @@ use crate::{ exceptions::{self, PyBaseException}, ffi, }; -use crate::{Borrowed, BoundObject, Py, PyAny, PyObject, Python}; +use crate::{Borrowed, BoundObject, Py, PyAny, Python}; use std::borrow::Cow; use std::ffi::CStr; @@ -98,14 +98,14 @@ impl<'py> DowncastIntoError<'py> { /// Helper conversion trait that allows to use custom arguments for lazy exception construction. pub trait PyErrArguments: Send + Sync { /// Arguments for exception - fn arguments(self, py: Python<'_>) -> PyObject; + fn arguments(self, py: Python<'_>) -> Py; } impl PyErrArguments for T where T: for<'py> IntoPyObject<'py> + Send + Sync, { - fn arguments(self, py: Python<'_>) -> PyObject { + fn arguments(self, py: Python<'_>) -> Py { // FIXME: `arguments` should become fallible match self.into_pyobject(py) { Ok(obj) => obj.into_any().unbind(), @@ -391,7 +391,7 @@ impl PyErr { name: &CStr, doc: Option<&CStr>, base: Option<&Bound<'py, PyType>>, - dict: Option, + dict: Option>, ) -> PyResult> { let base: *mut ffi::PyObject = match base { None => std::ptr::null_mut(), @@ -725,7 +725,7 @@ struct PyDowncastErrorArguments { } impl PyErrArguments for PyDowncastErrorArguments { - fn arguments(self, py: Python<'_>) -> PyObject { + fn arguments(self, py: Python<'_>) -> Py { const FAILED_TO_EXTRACT: Cow<'_, str> = Cow::Borrowed(""); let from = self.from.bind(py).qualname(); let from = match &from { diff --git a/src/impl_/callback.rs b/src/impl_/callback.rs index e8340c829a1..7ce01560aee 100644 --- a/src/impl_/callback.rs +++ b/src/impl_/callback.rs @@ -3,7 +3,7 @@ use crate::err::{PyErr, PyResult}; use crate::exceptions::PyOverflowError; use crate::ffi::{self, Py_hash_t}; -use crate::{BoundObject, IntoPyObject, PyObject, Python}; +use crate::{BoundObject, IntoPyObject, Py, PyAny, Python}; use std::ffi::c_int; /// A type which can be the return type of a python C-API callback @@ -106,12 +106,12 @@ impl IntoPyCallbackOutput<'_, usize> for usize { } } -impl<'py, T> IntoPyCallbackOutput<'py, PyObject> for T +impl<'py, T> IntoPyCallbackOutput<'py, Py> for T where T: IntoPyObject<'py>, { #[inline] - fn convert(self, py: Python<'py>) -> PyResult { + fn convert(self, py: Python<'py>) -> PyResult> { self.into_pyobject(py) .map(BoundObject::into_any) .map(BoundObject::unbind) diff --git a/src/impl_/pyclass.rs b/src/impl_/pyclass.rs index a158d5a1a59..1228c2ea758 100644 --- a/src/impl_/pyclass.rs +++ b/src/impl_/pyclass.rs @@ -29,7 +29,7 @@ mod lazy_type_object; mod probes; pub use assertions::*; -pub use lazy_type_object::LazyTypeObject; +pub use lazy_type_object::{type_object_init_failed, LazyTypeObject}; pub use probes::*; /// Gets the offset of the dictionary from the start of the object in bytes. diff --git a/src/impl_/pyclass/lazy_type_object.rs b/src/impl_/pyclass/lazy_type_object.rs index ac4aa3fd3d9..c58f4978451 100644 --- a/src/impl_/pyclass/lazy_type_object.rs +++ b/src/impl_/pyclass/lazy_type_object.rs @@ -11,12 +11,11 @@ use crate::types::PyTypeMethods; use crate::{ exceptions::PyRuntimeError, ffi, - impl_::pyclass::MaybeRuntimePyMethodDef, - impl_::pymethods::PyMethodDefType, + impl_::{pyclass::MaybeRuntimePyMethodDef, pymethods::PyMethodDefType}, pyclass::{create_type_object, PyClassTypeObject}, sync::GILOnceCell, types::PyType, - Bound, PyClass, PyErr, PyObject, PyResult, Python, + Bound, Py, PyAny, PyClass, PyErr, PyResult, Python, }; use std::sync::Mutex; @@ -33,7 +32,7 @@ struct LazyTypeObjectInner { // Threads which have begun initialization of the `tp_dict`. Used for // reentrant initialization detection. initializing_threads: Mutex>, - tp_dict_filled: GILOnceCell<()>, + fully_initialized_type: GILOnceCell>, } impl LazyTypeObject { @@ -44,7 +43,7 @@ impl LazyTypeObject { LazyTypeObjectInner { value: GILOnceCell::new(), initializing_threads: Mutex::new(Vec::new()), - tp_dict_filled: GILOnceCell::new(), + fully_initialized_type: GILOnceCell::new(), }, PhantomData, ) @@ -53,15 +52,18 @@ impl LazyTypeObject { impl LazyTypeObject { /// Gets the type object contained by this `LazyTypeObject`, initializing it if needed. - pub fn get_or_init<'py>(&self, py: Python<'py>) -> &Bound<'py, PyType> { - self.get_or_try_init(py).unwrap_or_else(|err| { - err.print(py); - panic!("failed to create type object for {}", T::NAME) - }) + #[inline] + pub fn get_or_try_init<'py>(&self, py: Python<'py>) -> PyResult<&Bound<'py, PyType>> { + if let Some(type_object) = self.0.fully_initialized_type.get(py) { + // Fast path + return Ok(type_object.bind(py)); + } + + self.try_init(py) } - /// Fallible version of the above. - pub(crate) fn get_or_try_init<'py>(&self, py: Python<'py>) -> PyResult<&Bound<'py, PyType>> { + #[cold] + fn try_init<'py>(&self, py: Python<'py>) -> PyResult<&Bound<'py, PyType>> { self.0 .get_or_try_init(py, create_type_object::, T::NAME, T::items_iter()) } @@ -117,7 +119,7 @@ impl LazyTypeObjectInner { // `tp_dict`, it can still request the type object through `get_or_init`, // but the `tp_dict` may appear empty of course. - if self.tp_dict_filled.get(py).is_some() { + if self.fully_initialized_type.get(py).is_some() { // `tp_dict` is already filled: ok. return Ok(()); } @@ -185,8 +187,8 @@ impl LazyTypeObjectInner { // Now we hold the GIL and we can assume it won't be released until we // return from the function. - let result = self.tp_dict_filled.get_or_try_init(py, move || { - let result = initialize_tp_dict(py, type_object.as_ptr(), items); + let result = self.fully_initialized_type.get_or_try_init(py, move || { + initialize_tp_dict(py, type_object.as_ptr(), items)?; #[cfg(Py_3_14)] if is_immutable_type { // freeze immutable types after __dict__ is initialized @@ -217,13 +219,13 @@ impl LazyTypeObjectInner { self.initializing_threads.lock().unwrap() }; threads.clear(); - result + Ok(type_object.clone().unbind()) }); if let Err(err) = result { return Err(wrap_in_runtime_error( py, - err.clone_ref(py), + err, format!("An error occurred while initializing `{name}.__dict__`"), )); } @@ -235,7 +237,7 @@ impl LazyTypeObjectInner { fn initialize_tp_dict( py: Python<'_>, type_object: *mut ffi::PyObject, - items: Vec<(&'static CStr, PyObject)>, + items: Vec<(&'static CStr, Py)>, ) -> PyResult<()> { // We hold the GIL: the dictionary update can be considered atomic from // the POV of other threads. @@ -250,6 +252,13 @@ fn initialize_tp_dict( // This is necessary for making static `LazyTypeObject`s unsafe impl Sync for LazyTypeObject {} +/// Used in the macro-expanded implementation of `type_object_raw` for `#[pyclass]` types +#[cold] +pub fn type_object_init_failed(py: Python<'_>, err: PyErr, type_name: &str) -> ! { + err.write_unraisable(py, None); + panic!("failed to create type object for `{type_name}`") +} + #[cold] fn wrap_in_runtime_error(py: Python<'_>, err: PyErr, message: String) -> PyErr { let runtime_err = PyRuntimeError::new_err(message); diff --git a/src/impl_/pymethods.rs b/src/impl_/pymethods.rs index 50d02b6a645..829869b0f1e 100644 --- a/src/impl_/pymethods.rs +++ b/src/impl_/pymethods.rs @@ -10,8 +10,8 @@ use crate::pyclass::boolean_struct::False; use crate::types::PyType; use crate::{ ffi, Bound, DowncastError, Py, PyAny, PyClass, PyClassGuard, PyClassGuardMut, - PyClassInitializer, PyErr, PyObject, PyRef, PyRefMut, PyResult, PyTraverseError, PyTypeCheck, - PyVisit, Python, + PyClassInitializer, PyErr, PyRef, PyRefMut, PyResult, PyTraverseError, PyTypeCheck, PyVisit, + Python, }; use std::ffi::CStr; use std::ffi::{c_int, c_void}; @@ -84,7 +84,7 @@ pub enum PyMethodType { PyCFunctionFastWithKeywords(ffi::PyCFunctionFastWithKeywords), } -pub type PyClassAttributeFactory = for<'p> fn(Python<'p>) -> PyResult; +pub type PyClassAttributeFactory = for<'p> fn(Python<'p>) -> PyResult>; // TODO: it would be nice to use CStr in these types, but then the constructors can't be const fn // until `CStr::from_bytes_with_nul_unchecked` is const fn. diff --git a/src/impl_/wrap.rs b/src/impl_/wrap.rs index 9d14d321ae2..9d3372cfe60 100644 --- a/src/impl_/wrap.rs +++ b/src/impl_/wrap.rs @@ -1,6 +1,8 @@ use std::{convert::Infallible, marker::PhantomData, ops::Deref}; -use crate::{ffi, types::PyNone, Bound, IntoPyObject, IntoPyObjectExt, PyObject, PyResult, Python}; +use crate::{ + ffi, types::PyNone, Bound, IntoPyObject, IntoPyObjectExt, Py, PyAny, PyResult, Python, +}; /// Used to wrap values in `Option` for default arguments. pub trait SomeWrap { @@ -89,7 +91,7 @@ impl<'py, T: IntoPyObject<'py>, E> IntoPyObjectConverter> { } #[inline] - pub fn map_into_pyobject(&self, py: Python<'py>, obj: PyResult) -> PyResult + pub fn map_into_pyobject(&self, py: Python<'py>, obj: PyResult) -> PyResult> where T: IntoPyObject<'py>, { @@ -126,7 +128,7 @@ impl UnknownReturnType { } #[inline] - pub fn map_into_pyobject<'py>(&self, _: Python<'py>, _: PyResult) -> PyResult + pub fn map_into_pyobject<'py>(&self, _: Python<'py>, _: PyResult) -> PyResult> where T: IntoPyObject<'py>, { diff --git a/src/instance.rs b/src/instance.rs index 29bac257fee..844405ba27e 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -1689,7 +1689,7 @@ impl Py { /// # use pyo3::{prelude::*, intern}; /// # /// #[pyfunction] - /// fn version(sys: Py, py: Python<'_>) -> PyResult { + /// fn version(sys: Py, py: Python<'_>) -> PyResult> { /// sys.getattr(py, intern!(py, "version")) /// } /// # @@ -1698,7 +1698,7 @@ impl Py { /// # version(sys, py).unwrap(); /// # }); /// ``` - pub fn getattr<'py, N>(&self, py: Python<'py>, attr_name: N) -> PyResult + pub fn getattr<'py, N>(&self, py: Python<'py>, attr_name: N) -> PyResult> where N: IntoPyObject<'py, Target = PyString>, { @@ -1715,10 +1715,10 @@ impl Py { /// # Example: `intern!`ing the attribute name /// /// ``` - /// # use pyo3::{intern, pyfunction, types::PyModule, IntoPyObjectExt, PyObject, Python, PyResult}; + /// # use pyo3::{intern, pyfunction, types::PyModule, IntoPyObjectExt, Py, PyAny, Python, PyResult}; /// # /// #[pyfunction] - /// fn set_answer(ob: PyObject, py: Python<'_>) -> PyResult<()> { + /// fn set_answer(ob: Py, py: Python<'_>) -> PyResult<()> { /// ob.setattr(py, intern!(py, "answer"), 42) /// } /// # @@ -1743,7 +1743,7 @@ impl Py { py: Python<'py>, args: A, kwargs: Option<&Bound<'py, PyDict>>, - ) -> PyResult + ) -> PyResult> where A: PyCallArgs<'py>, { @@ -1753,7 +1753,7 @@ impl Py { /// Calls the object with only positional arguments. /// /// This is equivalent to the Python expression `self(*args)`. - pub fn call1<'py, A>(&self, py: Python<'py>, args: A) -> PyResult + pub fn call1<'py, A>(&self, py: Python<'py>, args: A) -> PyResult> where A: PyCallArgs<'py>, { @@ -1763,7 +1763,7 @@ impl Py { /// Calls the object without arguments. /// /// This is equivalent to the Python expression `self()`. - pub fn call0(&self, py: Python<'_>) -> PyResult { + pub fn call0(&self, py: Python<'_>) -> PyResult> { self.bind(py).as_any().call0().map(Bound::unbind) } @@ -1779,7 +1779,7 @@ impl Py { name: N, args: A, kwargs: Option<&Bound<'py, PyDict>>, - ) -> PyResult + ) -> PyResult> where N: IntoPyObject<'py, Target = PyString>, A: PyCallArgs<'py>, @@ -1796,7 +1796,7 @@ impl Py { /// /// To avoid repeated temporary allocations of Python strings, the [`intern!`](crate::intern) /// macro can be used to intern `name`. - pub fn call_method1<'py, N, A>(&self, py: Python<'py>, name: N, args: A) -> PyResult + pub fn call_method1<'py, N, A>(&self, py: Python<'py>, name: N, args: A) -> PyResult> where N: IntoPyObject<'py, Target = PyString>, A: PyCallArgs<'py>, @@ -1813,7 +1813,7 @@ impl Py { /// /// To avoid repeated temporary allocations of Python strings, the [`intern!`](crate::intern) /// macro can be used to intern `name`. - pub fn call_method0<'py, N>(&self, py: Python<'py>, name: N) -> PyResult + pub fn call_method0<'py, N>(&self, py: Python<'py>, name: N) -> PyResult> where N: IntoPyObject<'py, Target = PyString>, { @@ -1941,7 +1941,7 @@ impl AsRef> for Py { } } -impl std::convert::From> for PyObject +impl std::convert::From> for Py where T: DerefToPyAny, { @@ -1951,7 +1951,7 @@ where } } -impl std::convert::From> for PyObject +impl std::convert::From> for Py where T: DerefToPyAny, { @@ -2065,9 +2065,10 @@ impl std::fmt::Debug for Py { /// safely sent between threads. /// /// See the documentation for [`Py`](struct.Py.html). +#[deprecated(since = "0.26.0", note = "use `Py` instead")] pub type PyObject = Py; -impl PyObject { +impl Py { /// Deprecated version of [`PyObject::cast_bound`] #[inline] #[deprecated(since = "0.26.0", note = "use `Py::cast_bound_unchecked` instead")] @@ -2163,7 +2164,7 @@ impl PyObject { #[cfg(test)] mod tests { - use super::{Bound, IntoPyObject, Py, PyObject}; + use super::{Bound, IntoPyObject, Py}; use crate::tests::common::generate_unique_module_name; use crate::types::{dict::IntoPyDict, PyAnyMethods, PyCapsule, PyDict, PyString}; use crate::{ffi, Borrowed, PyAny, PyResult, Python}; @@ -2239,7 +2240,7 @@ mod tests { #[test] fn test_call_for_non_existing_method() { Python::attach(|py| { - let obj: PyObject = PyDict::new(py).into(); + let obj: Py = PyDict::new(py).into(); assert!(obj.call_method0(py, "asdf").is_err()); assert!(obj .call_method(py, "nonexistent_method", (1,), None) @@ -2266,7 +2267,7 @@ mod tests { Python::attach(|py| { let dict: Py = PyDict::new(py).unbind(); let cnt = dict.get_refcnt(py); - let p: PyObject = dict.into(); + let p: Py = dict.into(); assert_eq!(p.get_refcnt(py), cnt); }); } @@ -2355,7 +2356,7 @@ a = A() Python::attach(|py| { let instance = py.eval(ffi::c_str!("object()"), None, None).unwrap(); let ptr = instance.as_ptr(); - let instance: PyObject = instance.clone().unbind(); + let instance: Py = instance.clone().unbind(); assert_eq!(instance.as_ptr(), ptr); }) } diff --git a/src/internal/state.rs b/src/internal/state.rs index 8095071af88..1aff268c7f9 100644 --- a/src/internal/state.rs +++ b/src/internal/state.rs @@ -338,17 +338,17 @@ fn decrement_attach_count() { mod tests { use super::*; - use crate::{ffi, types::PyAnyMethods, PyObject, Python}; + use crate::{ffi, types::PyAnyMethods, Py, PyAny, Python}; use std::ptr::NonNull; - fn get_object(py: Python<'_>) -> PyObject { + fn get_object(py: Python<'_>) -> Py { py.eval(ffi::c_str!("object()"), None, None) .unwrap() .unbind() } #[cfg(not(pyo3_disable_reference_pool))] - fn pool_dec_refs_does_not_contain(obj: &PyObject) -> bool { + fn pool_dec_refs_does_not_contain(obj: &Py) -> bool { !get_pool() .pending_decrefs .lock() @@ -359,7 +359,7 @@ mod tests { // With free-threading, threads can empty the POOL at any time, so this // function does not test anything meaningful #[cfg(not(any(pyo3_disable_reference_pool, Py_GIL_DISABLED)))] - fn pool_dec_refs_contains(obj: &PyObject) -> bool { + fn pool_dec_refs_contains(obj: &Py) -> bool { get_pool() .pending_decrefs .lock() @@ -527,7 +527,7 @@ mod tests { // Rebuild obj so that it can be dropped unsafe { - PyObject::from_owned_ptr( + Py::::from_owned_ptr( pool.python(), ffi::PyCapsule_GetPointer(capsule, std::ptr::null()) as _, ) diff --git a/src/lib.rs b/src/lib.rs index 55a17d9f0bd..34b4a6ae156 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -343,7 +343,9 @@ pub use crate::class::*; pub use crate::conversion::{FromPyObject, IntoPyObject, IntoPyObjectExt}; pub use crate::err::{DowncastError, DowncastIntoError, PyErr, PyErrArguments, PyResult, ToPyErr}; -pub use crate::instance::{Borrowed, Bound, BoundObject, Py, PyObject}; +#[allow(deprecated)] +pub use crate::instance::PyObject; +pub use crate::instance::{Borrowed, Bound, BoundObject, Py}; #[cfg(not(any(PyPy, GraalPy)))] #[allow(deprecated)] pub use crate::interpreter_lifecycle::{ diff --git a/src/marker.rs b/src/marker.rs index bf0a2fad3a1..ea75e3757a2 100644 --- a/src/marker.rs +++ b/src/marker.rs @@ -125,7 +125,7 @@ use crate::types::{ PyType, }; use crate::version::PythonVersionInfo; -use crate::{ffi, Bound, PyObject, PyTypeInfo}; +use crate::{ffi, Bound, Py, PyTypeInfo}; use std::ffi::CStr; use std::marker::PhantomData; @@ -689,21 +689,21 @@ impl<'py> Python<'py> { /// Gets the Python builtin value `None`. #[allow(non_snake_case)] // the Python keyword starts with uppercase #[inline] - pub fn None(self) -> PyObject { + pub fn None(self) -> Py { PyNone::get(self).to_owned().into_any().unbind() } /// Gets the Python builtin value `Ellipsis`, or `...`. #[allow(non_snake_case)] // the Python keyword starts with uppercase #[inline] - pub fn Ellipsis(self) -> PyObject { + pub fn Ellipsis(self) -> Py { PyEllipsis::get(self).to_owned().into_any().unbind() } /// Gets the Python builtin value `NotImplemented`. #[allow(non_snake_case)] // the Python keyword starts with uppercase #[inline] - pub fn NotImplemented(self) -> PyObject { + pub fn NotImplemented(self) -> Py { PyNotImplemented::get(self).to_owned().into_any().unbind() } diff --git a/src/prelude.rs b/src/prelude.rs index 258b522c103..31cf4b11b71 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -10,7 +10,9 @@ pub use crate::conversion::{FromPyObject, IntoPyObject}; pub use crate::err::{PyErr, PyResult}; -pub use crate::instance::{Borrowed, Bound, Py, PyObject}; +#[allow(deprecated)] +pub use crate::instance::PyObject; +pub use crate::instance::{Borrowed, Bound, Py}; pub use crate::marker::Python; pub use crate::pycell::{PyRef, PyRefMut}; pub use crate::pyclass_init::PyClassInitializer; diff --git a/src/tests/common.rs b/src/tests/common.rs index 683d68b1bf1..df5af81c247 100644 --- a/src/tests/common.rs +++ b/src/tests/common.rs @@ -123,8 +123,8 @@ with warnings.catch_warnings(record=True) as warning_record: #[cfg(all(feature = "macros", Py_3_8, not(Py_GIL_DISABLED)))] #[pyclass(crate = "pyo3")] pub struct UnraisableCapture { - pub capture: Option<(PyErr, PyObject)>, - old_hook: Option, + pub capture: Option<(PyErr, Py)>, + old_hook: Option>, } #[cfg(all(feature = "macros", Py_3_8, not(Py_GIL_DISABLED)))] diff --git a/src/types/any.rs b/src/types/any.rs index 4c3fc0b53fa..fb3d97b55ce 100644 --- a/src/types/any.rs +++ b/src/types/any.rs @@ -1850,7 +1850,7 @@ class SimpleClass: #[pymethods(crate = "crate")] impl GetattrFail { - fn __getattr__(&self, attr: PyObject) -> PyResult { + fn __getattr__(&self, attr: Py) -> PyResult> { Err(PyValueError::new_err(attr)) } } @@ -2091,7 +2091,7 @@ class SimpleClass: #[pymethods(crate = "crate")] impl DirFail { - fn __dir__(&self) -> PyResult { + fn __dir__(&self) -> PyResult> { Err(PyValueError::new_err("uh-oh!")) } } diff --git a/src/types/bytearray.rs b/src/types/bytearray.rs index 11f9e37137f..c85c20f12f4 100644 --- a/src/types/bytearray.rs +++ b/src/types/bytearray.rs @@ -322,7 +322,7 @@ impl<'py> TryFrom<&Bound<'py, PyAny>> for Bound<'py, PyByteArray> { #[cfg(test)] mod tests { use crate::types::{PyAnyMethods, PyByteArray, PyByteArrayMethods}; - use crate::{exceptions, Bound, PyAny, PyObject, Python}; + use crate::{exceptions, Bound, Py, PyAny, Python}; #[test] fn test_len() { @@ -378,7 +378,7 @@ mod tests { let src = b"Hello Python"; let bytearray = PyByteArray::new(py, src); - let ba: PyObject = bytearray.into(); + let ba: Py = bytearray.into(); let bytearray = PyByteArray::from(ba.bind(py)).unwrap(); assert_eq!(src, unsafe { bytearray.as_bytes() }); diff --git a/src/types/module.rs b/src/types/module.rs index ec6e15a2f92..ba4aa9428f7 100644 --- a/src/types/module.rs +++ b/src/types/module.rs @@ -9,7 +9,7 @@ use crate::types::{ any::PyAnyMethods, list::PyListMethods, PyAny, PyCFunction, PyDict, PyList, PyString, }; use crate::{ - exceptions, ffi, Borrowed, Bound, BoundObject, IntoPyObject, IntoPyObjectExt, PyObject, Python, + exceptions, ffi, Borrowed, Bound, BoundObject, IntoPyObject, IntoPyObjectExt, Py, Python, }; #[cfg(all(not(Py_LIMITED_API), Py_GIL_DISABLED))] use std::ffi::c_int; @@ -287,7 +287,7 @@ pub trait PyModuleMethods<'py>: crate::sealed::Sealed { /// instead. fn add_wrapped(&self, wrapper: &impl Fn(Python<'py>) -> T) -> PyResult<()> where - T: IntoPyCallbackOutput<'py, PyObject>; + T: IntoPyCallbackOutput<'py, Py>; /// Adds a submodule to a module. /// @@ -502,7 +502,7 @@ impl<'py> PyModuleMethods<'py> for Bound<'py, PyModule> { fn add_wrapped(&self, wrapper: &impl Fn(Python<'py>) -> T) -> PyResult<()> where - T: IntoPyCallbackOutput<'py, PyObject>, + T: IntoPyCallbackOutput<'py, Py>, { fn inner(module: &Bound<'_, PyModule>, object: Bound<'_, PyAny>) -> PyResult<()> { let name = object.getattr(__name__(module.py()))?; diff --git a/src/types/sequence.rs b/src/types/sequence.rs index d510f36552b..de6c25a193c 100644 --- a/src/types/sequence.rs +++ b/src/types/sequence.rs @@ -398,10 +398,10 @@ impl PyTypeCheck for PySequence { #[cfg(test)] mod tests { use crate::types::{PyAnyMethods, PyList, PySequence, PySequenceMethods, PyTuple}; - use crate::{ffi, IntoPyObject, PyObject, Python}; + use crate::{ffi, IntoPyObject, Py, PyAny, Python}; use std::ptr; - fn get_object() -> PyObject { + fn get_object() -> Py { // Convenience function for getting a single unique object Python::attach(|py| { let obj = py.eval(ffi::c_str!("object()"), None, None).unwrap(); diff --git a/src/types/string.rs b/src/types/string.rs index c30c2da4bf4..1baa6a18eeb 100644 --- a/src/types/string.rs +++ b/src/types/string.rs @@ -562,7 +562,7 @@ mod tests { use pyo3_ffi::c_str; use super::*; - use crate::{exceptions::PyLookupError, types::PyAnyMethods as _, IntoPyObject, PyObject}; + use crate::{exceptions::PyLookupError, types::PyAnyMethods as _, IntoPyObject}; #[test] fn test_to_cow_utf8() { @@ -606,7 +606,7 @@ mod tests { #[test] fn test_encode_utf8_surrogate() { Python::attach(|py| { - let obj: PyObject = py + let obj: Py = py .eval(ffi::c_str!(r"'\ud800'"), None, None) .unwrap() .into(); diff --git a/tests/test_arithmetics.rs b/tests/test_arithmetics.rs index a73be443a2e..3e0e7712ba4 100644 --- a/tests/test_arithmetics.rs +++ b/tests/test_arithmetics.rs @@ -527,7 +527,7 @@ impl RichComparisons2 { "RC2" } - fn __richcmp__(&self, other: &Bound<'_, PyAny>, op: CompareOp) -> PyResult { + fn __richcmp__(&self, other: &Bound<'_, PyAny>, op: CompareOp) -> PyResult> { match op { CompareOp::Eq => true .into_pyobject(other.py()) @@ -608,7 +608,7 @@ mod return_not_implemented { "RC_Self" } - fn __richcmp__(&self, other: PyRef<'_, Self>, _op: CompareOp) -> PyObject { + fn __richcmp__(&self, other: PyRef<'_, Self>, _op: CompareOp) -> Py { other.py().None() } diff --git a/tests/test_class_attributes.rs b/tests/test_class_attributes.rs index 5cad559cf8b..b1a019c0457 100644 --- a/tests/test_class_attributes.rs +++ b/tests/test_class_attributes.rs @@ -152,40 +152,10 @@ fn recursive_class_attributes() { } #[test] +#[cfg(all(Py_3_8, not(Py_GIL_DISABLED)))] // sys.unraisablehook not available until Python 3.8 fn test_fallible_class_attribute() { - use pyo3::{exceptions::PyValueError, types::PyString}; - - struct CaptureStdErr<'py> { - oldstderr: Bound<'py, PyAny>, - string_io: Bound<'py, PyAny>, - } - - impl<'py> CaptureStdErr<'py> { - fn new(py: Python<'py>) -> PyResult { - let sys = py.import("sys")?; - let oldstderr = sys.getattr("stderr")?; - let string_io = py.import("io")?.getattr("StringIO")?.call0()?; - sys.setattr("stderr", &string_io)?; - Ok(Self { - oldstderr, - string_io, - }) - } - - fn reset(self) -> PyResult { - let py = self.string_io.py(); - let payload = self - .string_io - .getattr("getvalue")? - .call0()? - .cast::()? - .to_cow()? - .into_owned(); - let sys = py.import("sys")?; - sys.setattr("stderr", self.oldstderr)?; - Ok(payload) - } - } + use common::UnraisableCapture; + use pyo3::exceptions::PyValueError; #[pyclass] struct BrokenClass; @@ -199,21 +169,29 @@ fn test_fallible_class_attribute() { } Python::attach(|py| { - let stderr = CaptureStdErr::new(py).unwrap(); + let capture = UnraisableCapture::install(py); assert!(std::panic::catch_unwind(|| py.get_type::()).is_err()); - assert_eq!( - stderr.reset().unwrap().trim(), - "\ -ValueError: failed to create class attribute - -The above exception was the direct cause of the following exception: -RuntimeError: An error occurred while initializing `BrokenClass.fails_to_init` + let (err, object) = capture.borrow_mut(py).capture.take().unwrap(); + assert!(object.is_none(py)); -The above exception was the direct cause of the following exception: + assert_eq!( + err.to_string(), + "RuntimeError: An error occurred while initializing class BrokenClass" + ); + let cause = err.cause(py).unwrap(); + assert_eq!( + cause.to_string(), + "RuntimeError: An error occurred while initializing `BrokenClass.fails_to_init`" + ); + let cause = cause.cause(py).unwrap(); + assert_eq!( + cause.to_string(), + "ValueError: failed to create class attribute" + ); + assert!(cause.cause(py).is_none()); -RuntimeError: An error occurred while initializing class BrokenClass" - ) + capture.borrow_mut(py).uninstall(py); }); } diff --git a/tests/test_class_basics.rs b/tests/test_class_basics.rs index d085db60351..027f050320e 100644 --- a/tests/test_class_basics.rs +++ b/tests/test_class_basics.rs @@ -207,13 +207,13 @@ struct ClassWithObjectField { // It used to be that PyObject was not supported with (get, set) // - this test is just ensuring it compiles. #[pyo3(get, set)] - value: PyObject, + value: Py, } #[pymethods] impl ClassWithObjectField { #[new] - fn new(value: PyObject) -> ClassWithObjectField { + fn new(value: Py) -> ClassWithObjectField { Self { value } } } @@ -719,14 +719,14 @@ fn test_unsendable_dict_with_weakref() { #[pyclass(generic)] struct ClassWithRuntimeParametrization { #[pyo3(get, set)] - value: PyObject, + value: Py, } #[cfg(Py_3_9)] #[pymethods] impl ClassWithRuntimeParametrization { #[new] - fn new(value: PyObject) -> ClassWithRuntimeParametrization { + fn new(value: Py) -> ClassWithRuntimeParametrization { Self { value } } } diff --git a/tests/test_class_new.rs b/tests/test_class_new.rs index 51db45f4774..ce1d9b83ef1 100644 --- a/tests/test_class_new.rs +++ b/tests/test_class_new.rs @@ -302,7 +302,7 @@ fn test_new_returns_bound() { #[pyo3::pyclass] struct NewClassMethod { #[pyo3(get)] - cls: pyo3::PyObject, + cls: pyo3::Py, } #[pyo3::pymethods] diff --git a/tests/test_gc.rs b/tests/test_gc.rs index 368fd3871bc..8f39c1baca1 100644 --- a/tests/test_gc.rs +++ b/tests/test_gc.rs @@ -151,7 +151,7 @@ fn data_is_dropped() { #[pyclass(subclass)] struct CycleWithClear { - cycle: Option, + cycle: Option>, _guard: DropGuard, } @@ -342,7 +342,7 @@ fn gc_during_borrow() { fn traverse_partial() { #[pyclass] struct PartialTraverse { - member: PyObject, + member: Py, } impl PartialTraverse { @@ -378,7 +378,7 @@ fn traverse_partial() { fn traverse_panic() { #[pyclass] struct PanickyTraverse { - member: PyObject, + member: Py, } impl PanickyTraverse { @@ -753,7 +753,7 @@ fn test_drop_buffer_during_traversal_without_gil() { #[pyclass] struct BufferDropDuringTraversal { inner: Mutex)>>, - cycle: Option, + cycle: Option>, } #[pymethods] diff --git a/tests/test_mapping.rs b/tests/test_mapping.rs index 8e958fc5c2f..bab7c0a418a 100644 --- a/tests/test_mapping.rs +++ b/tests/test_mapping.rs @@ -65,8 +65,8 @@ impl Mapping { &self, py: Python<'_>, key: &str, - default: Option, - ) -> PyResult> { + default: Option>, + ) -> PyResult>> { match self.index.get(key) { Some(value) => Ok(Some(value.into_pyobject(py)?.into_any().unbind())), None => Ok(default), diff --git a/tests/test_methods.rs b/tests/test_methods.rs index 5c96aa2afc6..06544f221e9 100644 --- a/tests/test_methods.rs +++ b/tests/test_methods.rs @@ -289,7 +289,7 @@ impl MethSignature { py: Python<'_>, a: i32, kwargs: Option<&Bound<'_, PyDict>>, - ) -> PyResult { + ) -> PyResult> { [ a.into_pyobject(py)?.into_any().into_bound(), kwargs.into_pyobject(py)?.into_any().into_bound(), @@ -304,7 +304,7 @@ impl MethSignature { py: Python<'_>, a: i32, kwargs: Option<&Bound<'_, PyDict>>, - ) -> PyResult { + ) -> PyResult> { [ a.into_pyobject(py)?.into_any().into_bound(), kwargs.into_pyobject(py)?.into_any().into_bound(), @@ -334,7 +334,7 @@ impl MethSignature { py: Python<'_>, args: &Bound<'_, PyTuple>, a: i32, - ) -> PyResult { + ) -> PyResult> { (args, a) .into_pyobject(py) .map(BoundObject::into_any) @@ -357,7 +357,7 @@ impl MethSignature { py: Python<'_>, a: i32, kwargs: Option<&Bound<'_, PyDict>>, - ) -> PyResult { + ) -> PyResult> { [ a.into_pyobject(py)?.into_any().into_bound(), kwargs.into_pyobject(py)?.into_any().into_bound(), @@ -922,9 +922,9 @@ fn test_from_sequence() { #[pyclass] struct r#RawIdents { #[pyo3(get, set)] - r#type: PyObject, - r#subtype: PyObject, - r#subsubtype: PyObject, + r#type: Py, + r#subtype: Py, + r#subsubtype: Py, } #[pymethods] @@ -932,9 +932,9 @@ impl r#RawIdents { #[new] pub fn r#new( r#_py: Python<'_>, - r#type: PyObject, - r#subtype: PyObject, - r#subsubtype: PyObject, + r#type: Py, + r#subtype: Py, + r#subsubtype: Py, ) -> Self { Self { r#type, @@ -944,36 +944,36 @@ impl r#RawIdents { } #[getter(r#subtype)] - pub fn r#get_subtype(&self, py: Python<'_>) -> PyObject { + pub fn r#get_subtype(&self, py: Python<'_>) -> Py { self.r#subtype.clone_ref(py) } #[setter(r#subtype)] - pub fn r#set_subtype(&mut self, r#subtype: PyObject) { + pub fn r#set_subtype(&mut self, r#subtype: Py) { self.r#subtype = r#subtype; } #[getter] - pub fn r#get_subsubtype(&self, py: Python<'_>) -> PyObject { + pub fn r#get_subsubtype(&self, py: Python<'_>) -> Py { self.r#subsubtype.clone_ref(py) } #[setter] - pub fn r#set_subsubtype(&mut self, r#subsubtype: PyObject) { + pub fn r#set_subsubtype(&mut self, r#subsubtype: Py) { self.r#subsubtype = r#subsubtype; } - pub fn r#__call__(&mut self, r#type: PyObject) { + pub fn r#__call__(&mut self, r#type: Py) { self.r#type = r#type; } #[staticmethod] - pub fn r#static_method(r#type: PyObject) -> PyObject { + pub fn r#static_method(r#type: Py) -> Py { r#type } #[classmethod] - pub fn r#class_method(_: &Bound<'_, PyType>, r#type: PyObject) -> PyObject { + pub fn r#class_method(_: &Bound<'_, PyType>, r#type: Py) -> Py { r#type } diff --git a/tests/test_module.rs b/tests/test_module.rs index 9b316134752..2f5766aadaf 100644 --- a/tests/test_module.rs +++ b/tests/test_module.rs @@ -323,7 +323,7 @@ fn test_module_nesting() { // Test that argument parsing specification works for pyfunctions #[pyfunction(signature = (a=5, *args))] -fn ext_vararg_fn(py: Python<'_>, a: i32, args: &Bound<'_, PyTuple>) -> PyResult { +fn ext_vararg_fn(py: Python<'_>, a: i32, args: &Bound<'_, PyTuple>) -> PyResult> { [ a.into_pyobject(py)?.into_any().into_bound(), args.as_any().clone(), @@ -335,7 +335,7 @@ fn ext_vararg_fn(py: Python<'_>, a: i32, args: &Bound<'_, PyTuple>) -> PyResult< #[pymodule] fn vararg_module(m: &Bound<'_, PyModule>) -> PyResult<()> { #[pyfn(m, signature = (a=5, *args))] - fn int_vararg_fn(py: Python<'_>, a: i32, args: &Bound<'_, PyTuple>) -> PyResult { + fn int_vararg_fn(py: Python<'_>, a: i32, args: &Bound<'_, PyTuple>) -> PyResult> { ext_vararg_fn(py, a, args) } diff --git a/tests/test_proto_methods.rs b/tests/test_proto_methods.rs index e5e36939c53..e8df772faf1 100644 --- a/tests/test_proto_methods.rs +++ b/tests/test_proto_methods.rs @@ -21,7 +21,7 @@ struct ExampleClass { #[pymethods] impl ExampleClass { - fn __getattr__(&self, py: Python<'_>, attr: &str) -> PyResult { + fn __getattr__(&self, py: Python<'_>, attr: &str) -> PyResult> { if attr == "special_custom_attr" { Ok(self.custom_attr.into_pyobject(py)?.into_any().unbind()) } else { @@ -252,7 +252,7 @@ enum SequenceIndex<'py> { #[pyclass] pub struct Sequence { - values: Vec, + values: Vec>, } #[pymethods] @@ -261,7 +261,7 @@ impl Sequence { self.values.len() } - fn __getitem__(&self, index: SequenceIndex<'_>, py: Python<'_>) -> PyResult { + fn __getitem__(&self, index: SequenceIndex<'_>, py: Python<'_>) -> PyResult> { match index { SequenceIndex::Integer(index) => { let uindex = self.usize_index(index)?; @@ -275,7 +275,7 @@ impl Sequence { } } - fn __setitem__(&mut self, index: isize, value: PyObject) -> PyResult<()> { + fn __setitem__(&mut self, index: isize, value: Py) -> PyResult<()> { let uindex = self.usize_index(index)?; self.values .get_mut(uindex) @@ -293,7 +293,7 @@ impl Sequence { } } - fn append(&mut self, value: PyObject) { + fn append(&mut self, value: Py) { self.values.push(value); } } @@ -634,14 +634,14 @@ fn getattr_and_getattribute() { #[pyclass] #[derive(Debug)] struct OnceFuture { - future: PyObject, + future: Py, polled: bool, } #[pymethods] impl OnceFuture { #[new] - fn new(future: PyObject) -> Self { + fn new(future: Py) -> Self { OnceFuture { future, polled: false, @@ -828,7 +828,7 @@ struct NotHashable; #[pymethods] impl NotHashable { #[classattr] - const __hash__: Option = None; + const __hash__: Option> = None; } #[test] @@ -850,7 +850,7 @@ struct DefaultedContains; #[pymethods] impl DefaultedContains { - fn __iter__(&self, py: Python<'_>) -> PyObject { + fn __iter__(&self, py: Python<'_>) -> Py { PyList::new(py, ["a", "b", "c"]) .unwrap() .as_any() @@ -865,7 +865,7 @@ struct NoContains; #[pymethods] impl NoContains { - fn __iter__(&self, py: Python<'_>) -> PyObject { + fn __iter__(&self, py: Python<'_>) -> Py { PyList::new(py, ["a", "b", "c"]) .unwrap() .as_any() @@ -877,7 +877,7 @@ impl NoContains { // Equivalent to the opt-out const form in NotHashable above, just more verbose, to confirm this // also works. #[classattr] - fn __contains__() -> Option { + fn __contains__() -> Option> { None } } diff --git a/tests/test_pyself.rs b/tests/test_pyself.rs index 4eff50b4770..3f7d3844fce 100644 --- a/tests/test_pyself.rs +++ b/tests/test_pyself.rs @@ -64,7 +64,7 @@ impl Iter { slf } - fn __next__(mut slf: PyRefMut<'_, Self>) -> PyResult> { + fn __next__(mut slf: PyRefMut<'_, Self>) -> PyResult>> { let bytes = slf.keys.bind(slf.py()).as_bytes(); match bytes.get(slf.idx) { Some(&b) => { diff --git a/tests/test_sequence.rs b/tests/test_sequence.rs index 2d0344d6d58..ca1b15ae9a5 100644 --- a/tests/test_sequence.rs +++ b/tests/test_sequence.rs @@ -257,7 +257,7 @@ fn test_inplace_repeat() { #[pyclass] struct AnyObjectList { #[pyo3(get, set)] - items: Vec, + items: Vec>, } #[test] @@ -372,7 +372,7 @@ fn sequence_length() { #[pyclass(generic, sequence)] struct GenericList { #[pyo3(get, set)] - items: Vec, + items: Vec>, } #[cfg(Py_3_10)] @@ -382,7 +382,7 @@ impl GenericList { self.items.len() } - fn __getitem__(&self, idx: isize) -> PyResult { + fn __getitem__(&self, idx: isize) -> PyResult> { match self.items.get(idx as usize) { Some(x) => pyo3::Python::attach(|py| Ok(x.clone_ref(py))), None => Err(PyIndexError::new_err("Index out of bounds")), @@ -402,7 +402,7 @@ fn test_generic_both_subscriptions_types() { GenericList { items: [1, 2, 3] .iter() - .map(|x| -> PyObject { + .map(|x| -> Py { let x: Result, Infallible> = x.into_pyobject(py); x.unwrap().into_any().unbind() }) diff --git a/tests/ui/ambiguous_associated_items.rs b/tests/ui/ambiguous_associated_items.rs index f553ba1f33f..34ebd931ce3 100644 --- a/tests/ui/ambiguous_associated_items.rs +++ b/tests/ui/ambiguous_associated_items.rs @@ -10,16 +10,16 @@ pub enum SimpleItems { #[pyclass] pub enum ComplexItems { - Error(PyObject), - Output(PyObject), - Target(PyObject), + Error(Py), + Output(Py), + Target(Py), } #[derive(IntoPyObject)] enum DeriveItems { - Error(PyObject), - Output(PyObject), - Target(PyObject), + Error(Py), + Output(Py), + Target(Py), } fn main() {} diff --git a/tests/ui/invalid_pyclass_args.rs b/tests/ui/invalid_pyclass_args.rs index 221fc6ca35f..a4782fceb2e 100644 --- a/tests/ui/invalid_pyclass_args.rs +++ b/tests/ui/invalid_pyclass_args.rs @@ -45,7 +45,7 @@ impl EqOptAndManualRichCmp { _py: Python, _other: Bound<'_, PyAny>, _op: pyo3::pyclass::CompareOp, - ) -> PyResult { + ) -> PyResult> { todo!() } } diff --git a/tests/ui/invalid_pyclass_generic.rs b/tests/ui/invalid_pyclass_generic.rs index 45cd5fec9b0..dac8c057d81 100644 --- a/tests/ui/invalid_pyclass_generic.rs +++ b/tests/ui/invalid_pyclass_generic.rs @@ -2,8 +2,7 @@ use pyo3::prelude::*; use pyo3::types::PyType; #[pyclass(generic)] -struct ClassRedefinesClassGetItem { -} +struct ClassRedefinesClassGetItem {} #[pymethods] impl ClassRedefinesClassGetItem { @@ -16,7 +15,7 @@ impl ClassRedefinesClassGetItem { pub fn __class_getitem__( cls: &Bound<'_, PyType>, key: &Bound<'_, PyAny>, - ) -> PyResult { + ) -> PyResult> { pyo3::types::PyGenericAlias::new(cls.py(), cls.as_any(), key) } } diff --git a/tests/ui/invalid_pyclass_generic.stderr b/tests/ui/invalid_pyclass_generic.stderr index 1381c31e1e1..1d6233102e7 100644 --- a/tests/ui/invalid_pyclass_generic.stderr +++ b/tests/ui/invalid_pyclass_generic.stderr @@ -4,7 +4,7 @@ error[E0592]: duplicate definitions with name `__pymethod___class_getitem____` 4 | #[pyclass(generic)] | ^^^^^^^^^^^^^^^^^^^ duplicate definitions for `__pymethod___class_getitem____` ... -8 | #[pymethods] +7 | #[pymethods] | ------------ other definition for `__pymethod___class_getitem____` | = note: this error originates in the attribute macro `pyclass` (in Nightly builds, run with -Z macro-backtrace for more info) @@ -15,11 +15,11 @@ error[E0592]: duplicate definitions with name `__class_getitem__` 4 | #[pyclass(generic)] | ^^^^^^^^^^^^^^^^^^^ duplicate definitions for `__class_getitem__` ... -16 | / pub fn __class_getitem__( -17 | | cls: &Bound<'_, PyType>, -18 | | key: &Bound<'_, PyAny>, -19 | | ) -> PyResult { - | |___________________________- other definition for `__class_getitem__` +15 | / pub fn __class_getitem__( +16 | | cls: &Bound<'_, PyType>, +17 | | key: &Bound<'_, PyAny>, +18 | | ) -> PyResult> { + | |____________________________- other definition for `__class_getitem__` | = note: this error originates in the attribute macro `pyclass` (in Nightly builds, run with -Z macro-backtrace for more info) @@ -35,9 +35,9 @@ note: candidate #1 is defined in an impl for the type `ClassRedefinesClassGetIte 4 | #[pyclass(generic)] | ^^^^^^^^^^^^^^^^^^^ note: candidate #2 is defined in an impl for the type `ClassRedefinesClassGetItem` - --> tests/ui/invalid_pyclass_generic.rs:8:1 + --> tests/ui/invalid_pyclass_generic.rs:7:1 | -8 | #[pymethods] +7 | #[pymethods] | ^^^^^^^^^^^^ = note: this error originates in the attribute macro `pyclass` which comes from the expansion of the attribute macro `pymethods` (in Nightly builds, run with -Z macro-backtrace for more info) @@ -53,13 +53,13 @@ note: candidate #1 is defined in an impl for the type `ClassRedefinesClassGetIte 4 | #[pyclass(generic)] | ^^^^^^^^^^^^^^^^^^^ note: candidate #2 is defined in an impl for the type `ClassRedefinesClassGetItem` - --> tests/ui/invalid_pyclass_generic.rs:16:5 + --> tests/ui/invalid_pyclass_generic.rs:15:5 | -16 | / pub fn __class_getitem__( -17 | | cls: &Bound<'_, PyType>, -18 | | key: &Bound<'_, PyAny>, -19 | | ) -> PyResult { - | |___________________________^ +15 | / pub fn __class_getitem__( +16 | | cls: &Bound<'_, PyType>, +17 | | key: &Bound<'_, PyAny>, +18 | | ) -> PyResult> { + | |____________________________^ = note: this error originates in the attribute macro `pyclass` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0034]: multiple applicable items in scope @@ -75,20 +75,20 @@ error[E0034]: multiple applicable items in scope = note: this error originates in the attribute macro `pyclass` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0308]: mismatched types - --> tests/ui/invalid_pyclass_generic.rs:20:9 + --> tests/ui/invalid_pyclass_generic.rs:19:9 | -19 | ) -> PyResult { - | ------------------ expected `Result, PyErr>` because of return type -20 | pyo3::types::PyGenericAlias::new(cls.py(), cls.as_any(), key) +18 | ) -> PyResult> { + | ------------------- expected `Result, PyErr>` because of return type +19 | pyo3::types::PyGenericAlias::new(cls.py(), cls.as_any(), key) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `Result, PyErr>`, found `Result, PyErr>` | - = note: expected enum `Result, PyErr>` + = note: expected enum `Result, PyErr>` found enum `Result, PyErr>` error[E0034]: multiple applicable items in scope - --> tests/ui/invalid_pyclass_generic.rs:16:12 + --> tests/ui/invalid_pyclass_generic.rs:15:12 | -16 | pub fn __class_getitem__( +15 | pub fn __class_getitem__( | ^^^^^^^^^^^^^^^^^ multiple `__pymethod___class_getitem____` found | note: candidate #1 is defined in an impl for the type `ClassRedefinesClassGetItem` @@ -97,16 +97,16 @@ note: candidate #1 is defined in an impl for the type `ClassRedefinesClassGetIte 4 | #[pyclass(generic)] | ^^^^^^^^^^^^^^^^^^^ note: candidate #2 is defined in an impl for the type `ClassRedefinesClassGetItem` - --> tests/ui/invalid_pyclass_generic.rs:8:1 + --> tests/ui/invalid_pyclass_generic.rs:7:1 | -8 | #[pymethods] +7 | #[pymethods] | ^^^^^^^^^^^^ = note: this error originates in the attribute macro `pyclass` which comes from the expansion of the attribute macro `pymethods` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0034]: multiple applicable items in scope - --> tests/ui/invalid_pyclass_generic.rs:16:12 + --> tests/ui/invalid_pyclass_generic.rs:15:12 | -16 | pub fn __class_getitem__( +15 | pub fn __class_getitem__( | ^^^^^^^^^^^^^^^^^ multiple `__class_getitem__` found | note: candidate #1 is defined in an impl for the type `ClassRedefinesClassGetItem` @@ -115,19 +115,19 @@ note: candidate #1 is defined in an impl for the type `ClassRedefinesClassGetIte 4 | #[pyclass(generic)] | ^^^^^^^^^^^^^^^^^^^ note: candidate #2 is defined in an impl for the type `ClassRedefinesClassGetItem` - --> tests/ui/invalid_pyclass_generic.rs:16:5 + --> tests/ui/invalid_pyclass_generic.rs:15:5 | -16 | / pub fn __class_getitem__( -17 | | cls: &Bound<'_, PyType>, -18 | | key: &Bound<'_, PyAny>, -19 | | ) -> PyResult { - | |___________________________^ +15 | / pub fn __class_getitem__( +16 | | cls: &Bound<'_, PyType>, +17 | | key: &Bound<'_, PyAny>, +18 | | ) -> PyResult> { + | |____________________________^ = note: this error originates in the attribute macro `pyclass` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0034]: multiple applicable items in scope - --> tests/ui/invalid_pyclass_generic.rs:19:10 + --> tests/ui/invalid_pyclass_generic.rs:18:10 | -19 | ) -> PyResult { +18 | ) -> PyResult> { | ^^^^^^^^ multiple `wrap` found | = note: candidate #1 is defined in an impl for the type `IntoPyObjectConverter>`