#pyo3 #python #async-io #async #python-bindings #events

pyo3-asyncio-0-21

PyO3 适用于 Python 的 Asyncio 库的工具 - 0.21 分支

1 个不稳定版本

0.21.0 2024年6月13日

#131FFI

Download history 1189/week @ 2024-06-11 2317/week @ 2024-06-18 3050/week @ 2024-06-25 2613/week @ 2024-07-02 3388/week @ 2024-07-09 2185/week @ 2024-07-16 4969/week @ 2024-07-23 4305/week @ 2024-07-30 4232/week @ 2024-08-06

15,952 每月下载量

Apache-2.0

220KB
2.5K SLoC

PyO3 Asyncio

Actions Status codecov crates.io minimum rustc 1.63

这是从 pyo3-asyncio 分支出来的,以提供与 PyO3 0.21 的兼容性。这可能是未来永久分支的基础,具体取决于原始 pyo3-asyncio 维护者的状态。

Rust 绑定用于 PythonAsyncio 库。这个 crate 促进了 Rust Futures 和 Python Coroutines 之间的交互,并管理它们对应的事件循环的生命周期。

PyO3 Asyncio 是 PyO3 生态系统中的新成员。欢迎为此 crate 提交任何功能请求或错误修复的问题。

如果您是新手,最好的开始方式是阅读下面的入门指南!对于 v0.13v0.14 的用户,我强烈建议阅读 迁移部分,以了解 v0.14v0.15 中发生了什么变化。

使用说明

与 PyO3 一样,PyO3 Asyncio 支持以下软件版本

  • Python 3.7 及以上版本(CPython 和 PyPy)
  • Rust 1.48 及以上版本

PyO3 Asyncio 入门指南

如果您正在使用一个利用异步函数的 Python 库,或者希望为异步 Rust 库提供 Python 绑定,那么 pyo3-asyncio 可能正是您所需要的工具。它提供了在 Python 和 Rust 之间异步函数的转换,并支持流行的 Rust 运行时,如 tokioasync-std。此外,所有异步 Python 代码都在默认的 asyncio 事件循环上运行,因此 pyo3-asyncio 应该与现有的 Python 库很好地协同工作。

在接下来的章节中,我们将对 pyo3-asyncio 进行概述,解释如何使用 PyO3 调用异步 Python 函数,如何从 Python 调用异步 Rust 函数,以及如何配置代码库来管理这两个运行时。

快速入门

以下是一些立即开始使用的示例!这些示例中概念的更详细解释可以在接下来的章节中找到。

Rust 应用程序

在这里,我们初始化运行时,导入 Python 的 asyncio 库,并使用 Python 的默认 EventLoopasync-std 运行给定的 future,直到完成。在 future 中,我们将 asyncio 睡眠转换为 Rust future 并等待它。

# Cargo.toml dependencies
[dependencies]
pyo3 = { version = "0.20" }
pyo3-asyncio-0-21 = { version = "0.20", features = ["attributes", "async-std-runtime"] }
async-std = "1.9"
//! main.rs

use pyo3::prelude::*;

#[pyo3_asyncio_0_21::async_std::main]
async fn main() -> PyResult<()> {
    let fut = Python::with_gil(|py| {
        let asyncio = py.import_bound("asyncio")?;
        // convert asyncio.sleep into a Rust Future
        pyo3_asyncio_0_21::async_std::into_future(asyncio.call_method1("sleep", (1.into_py(py),))?)
    })?;

    fut.await?;

    Ok(())
}

同样的应用程序可以写成使用 tokio,通过使用 #[pyo3_asyncio_0_21::tokio::main] 属性。

# Cargo.toml dependencies
[dependencies]
pyo3 = { version = "0.20" }
pyo3-asyncio-0-21 = { version = "0.20", features = ["attributes", "tokio-runtime"] }
tokio = "1.9"
//! main.rs

use pyo3::prelude::*;

#[pyo3_asyncio_0_21::tokio::main]
async fn main() -> PyResult<()> {
    let fut = Python::with_gil(|py| {
        let asyncio = py.import_bound("asyncio")?;
        // convert asyncio.sleep into a Rust Future
        pyo3_asyncio_0_21::tokio::into_future(asyncio.call_method1("sleep", (1.into_py(py),))?)
    })?;

    fut.await?;

    Ok(())
}

有关此库的更多使用细节,请参阅 API 文档 和下面的简介。

PyO3 本地 Rust 模块

PyO3 Asyncio 还可以用来编写具有异步函数的本地模块。

[lib] 部分添加到 Cargo.toml,使您的库成为一个 Python 可以导入的 cdylib

[lib]
name = "my_async_module"
crate-type = ["cdylib"]

使用启用了 extension-module 功能的 pyo3 使您的项目依赖,并选择您的 pyo3-asyncio 运行时

对于 async-std

[dependencies]
pyo3 = { version = "0.20", features = ["extension-module"] }
pyo3-asyncio-0-21 = { version = "0.20", features = ["async-std-runtime"] }
async-std = "1.9"

对于 tokio

[dependencies]
pyo3 = { version = "0.20", features = ["extension-module"] }
pyo3-asyncio-0-21 = { version = "0.20", features = ["tokio-runtime"] }
tokio = "1.9"

导出一个使用 async-std 的异步函数

//! lib.rs

use pyo3::{prelude::*, wrap_pyfunction};

#[pyfunction]
fn rust_sleep(py: Python) -> PyResult<Bound<PyAny>> {
    pyo3_asyncio_0_21::async_std::future_into_py(py, async {
        async_std::task::sleep(std::time::Duration::from_secs(1)).await;
        Ok(())
    })
}

#[pymodule]
fn my_async_module(py: Python, m: &PyModule) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(rust_sleep, m)?)?;

    Ok(())
}

如果您想使用 tokio,则您的模块应如下所示

//! lib.rs

use pyo3::{prelude::*, wrap_pyfunction};

#[pyfunction]
fn rust_sleep(py: Python) -> PyResult<Bound<PyAny>> {
    pyo3_asyncio_0_21::tokio::future_into_py(py, async {
        tokio::time::sleep(std::time::Duration::from_secs(1)).await;
        Ok(())
    })
}

#[pymodule]
fn my_async_module(py: Python, m: &PyModule) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(rust_sleep, m)?)?;
    Ok(())
}

您可以使用 maturin(有关设置说明,请参阅 PyO3 指南中的 使用 Rust 的 Python 部分)构建您的模块。之后,您应该能够运行 Python REPL 来尝试它。

maturin develop && python3
🔗 Found pyo3 bindings
🐍 Found CPython 3.8 at python3
    Finished dev [unoptimized + debuginfo] target(s) in 0.04s
Python 3.8.5 (default, Jan 27 2021, 15:41:15)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import asyncio
>>>
>>> from my_async_module import rust_sleep
>>>
>>> async def main():
>>>     await rust_sleep()
>>>
>>> # should sleep for 1s
>>> asyncio.run(main())
>>>

在 Rust 中等待异步 Python 函数

让我们看看一个简单的异步 Python 函数

# Sleep for 1 second
async def py_sleep():
    await asyncio.sleep(1)

Python 中的异步函数只是返回一个 coroutine 对象的函数。对于我们来说,我们实际上并不需要了解太多关于这些 coroutine 对象。关键因素是调用一个 async 函数的方式与调用一个普通函数的方式相同,唯一的区别是我们必须对它返回的对象做些特殊处理。

通常在 Python 中,这种特殊处理是 await 关键字,但为了在 Rust 中等待这个协程,我们首先需要将其转换为 Rust 的 coroutine 版本:一个 Future。这就是 pyo3-asyncio 发挥作用的地方。`pyo3_asyncio_0_21::into_future` 为我们执行这个转换。

use pyo3::prelude::*;

#[pyo3_asyncio_0_21::tokio::main]
async fn main() -> PyResult<()> {
    let future = Python::with_gil(|py| -> PyResult<_> {
        // import the module containing the py_sleep function
        let example = py.import_bound("example")?;

        // calling the py_sleep method like a normal function
        // returns a coroutine
        let coroutine = example.call_method0("py_sleep")?;

        // convert the coroutine into a Rust future using the
        // tokio runtime
        pyo3_asyncio_0_21::tokio::into_future(coroutine)
    })?;

    // await the future
    future.await?;

    Ok(())
}

如果您想了解更多有关通用 协程awaitables 的信息,请查看 Python 3 asyncio 文档 以获取更多信息。

在 Python 中等待 Rust 未来

这里我们有与之前相同的异步函数,使用 async-std 运行时在 Rust 中编写

/// Sleep for 1 second
async fn rust_sleep() {
    async_std::task::sleep(std::time::Duration::from_secs(1)).await;
}

类似于 Python,Rust 的异步函数也返回一个称为 Future 的特殊对象

let future = rust_sleep();

我们可以将这个 Future 对象转换为 Python,使其成为 awaitable。这告诉 Python 您可以使用 await 关键字与它一起使用。为了做到这一点,我们将调用 pyo3_asyncio_0_21::async_std::future_into_py

use pyo3::prelude::*;

async fn rust_sleep() {
    async_std::task::sleep(std::time::Duration::from_secs(1)).await;
}

#[pyfunction]
fn call_rust_sleep(py: Python) -> PyResult<Bound<PyAny>> {
    pyo3_asyncio_0_21::async_std::future_into_py(py, async move {
        rust_sleep().await;
        Ok(())
    })
}

在 Python 中,我们可以像调用任何其他异步函数一样调用这个 pyo3 函数

from example import call_rust_sleep

async def rust_sleep():
    await call_rust_sleep()

管理事件循环

Python 的事件循环需要一些特殊处理,特别是关于主线程。Python 的某些 asyncio 功能,如适当的信号处理,需要控制主线程,而这通常与 Rust 不兼容。

幸运的是,Rust 的事件循环非常灵活,且 不需要 控制主线程,因此在我们 pyo3-asyncio 中,我们决定最佳方式来处理 Rust/Python 互操作是仅仅将主线程让给 Python,并在后台运行 Rust 的事件循环。遗憾的是,由于大多数事件循环实现 偏好 控制主线程,这仍然可能使某些事情变得尴尬。

PyO3 Asyncio 初始化

由于 Python 需要控制主线程,我们无法使用 Rust 运行时提供的方便的 proc macros 来处理 main 函数或 #[test] 函数。相反,PyO3 的初始化必须从 main 函数中完成,并且主线程必须阻塞在 pyo3_asyncio_0_21::run_foreverpyo3_asyncio_0_21::async_std::run_until_complete 上。

由于我们必须在这些函数上阻塞,所以我们不能使用 #[async_std::main]#[tokio::main],因为在一个异步函数中进行长阻塞调用不是一个好主意。

内部,这些 #[main] proc macros 被扩展为类似这样的内容

fn main() {
    // your async main fn
    async fn _main_impl() { /* ... */ }
    Runtime::new().block_on(_main_impl());
}

在由 block_on 驱动的 Future 中进行长时间的阻塞调用会阻止该线程执行其他操作,并可能对某些运行时造成麻烦(实际上,这也会导致单线程运行时死锁!)。许多运行时都提供某种 spawn_blocking 机制来避免这个问题,但同样,我们在这里无法使用它,因为我们需要在 线程上进行阻塞。

因此,pyo3-asyncio 提供了自己的 proc macros 来提供这种初始化。这些宏旨在模仿 async-stdtokio 的初始化,同时满足 Python 运行时的需求。

以下是一个使用 async-std 运行时的 PyO3 初始化的完整示例

use pyo3::prelude::*;

#[pyo3_asyncio_0_21::async_std::main]
async fn main() -> PyResult<()> {
    // PyO3 is initialized - Ready to go

    let fut = Python::with_gil(|py| -> PyResult<_> {
        let asyncio = py.import_bound("asyncio")?;

        // convert asyncio.sleep into a Rust Future
        pyo3_asyncio_0_21::async_std::into_future(
            asyncio.call_method1("sleep", (1.into_py(py),))?
        )
    })?;

    fut.await?;

    Ok(())
}

关于 asyncio.run 的说明

在 Python 3.7+ 中,使用 asyncio 运行顶层协程的推荐方法是使用 asyncio.run。在 v0.13 中,由于初始化问题,我们不建议使用此函数,但在 v0.14 中,使用此函数是完全可以的...但有条件。

由于我们的 Rust <--> Python 转换需要 Python 事件循环的引用,这引发了一个问题。想象一下,我们有一个 PyO3 Asyncio 模块,它定义了一个像之前示例中的 rust_sleep 函数。你可能会正确地假设你可以像这样直接将其传递给 asyncio.run

import asyncio

from my_async_module import rust_sleep

asyncio.run(rust_sleep())

你可能惊讶地发现这会引发一个错误

Traceback (most recent call last):
  File "example.py", line 5, in <module>
    asyncio.run(rust_sleep())
RuntimeError: no running event loop

这里发生的事情是我们调用 rust_sleep 在它实际上在由 asyncio.run 创建的事件循环上运行之前。这是反直觉的,但这是预期的行为,不幸的是,在 PyO3 Asyncio 本身内似乎没有很好的方法来解决此问题。

然而,我们可以通过一个简单的折衷方案使这个例子工作

import asyncio

from my_async_module import rust_sleep

# Calling main will just construct the coroutine that later calls rust_sleep.
# - This ensures that rust_sleep will be called when the event loop is running,
#   not before.
async def main():
    await rust_sleep()

# Run the main() coroutine at the top-level instead
asyncio.run(main())

非标准 Python 事件循环

Python 允许你使用替代默认的 asyncio 事件循环。一个流行的替代方案是 uvloop。在 v0.13 中,使用非标准事件循环有些麻烦,但在 v0.14 中,这变得非常简单。

在 PyO3 Asyncio 本地扩展中使用 uvloop

# Cargo.toml

[lib]
name = "my_async_module"
crate-type = ["cdylib"]

[dependencies]
pyo3 = { version = "0.20", features = ["extension-module"] }
pyo3-asyncio-0-21 = { version = "0.20", features = ["tokio-runtime"] }
async-std = "1.9"
tokio = "1.9"
//! lib.rs

use pyo3::{prelude::*, wrap_pyfunction};

#[pyfunction]
fn rust_sleep(py: Python) -> PyResult<Bound<PyAny>> {
    pyo3_asyncio_0_21::tokio::future_into_py(py, async {
        tokio::time::sleep(std::time::Duration::from_secs(1)).await;
        Ok(())
    })
}

#[pymodule]
fn my_async_module(_py: Python, m: &PyModule) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(rust_sleep, m)?)?;

    Ok(())
}
$ maturin develop && python3
🔗 Found pyo3 bindings
🐍 Found CPython 3.8 at python3
    Finished dev [unoptimized + debuginfo] target(s) in 0.04s
Python 3.8.8 (default, Apr 13 2021, 19:58:26)
[GCC 7.3.0] :: Anaconda, Inc. on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import asyncio
>>> import uvloop
>>>
>>> import my_async_module
>>>
>>> uvloop.install()
>>>
>>> async def main():
...     await my_async_module.rust_sleep()
...
>>> asyncio.run(main())
>>>

在 Rust 应用程序中使用 uvloop

在 Rust 应用程序中使用 uvloop 稍微复杂一些,但通过相对较少的修改仍然可行。

不幸的是,我们无法在非标准事件循环中使用 #[pyo3_asyncio_0_21::<runtime>::main] 属性。这是因为 #[pyo3_asyncio_0_21::<runtime>::main] proc macro 必须在安装 uvloop 策略之前与 Python 事件循环交互。

[dependencies]
async-std = "1.9"
pyo3 = "0.20"
pyo3-asyncio-0-21 = { version = "0.20", features = ["async-std-runtime"] }
//! main.rs

use pyo3::{prelude::*, types::PyType};

fn main() -> PyResult<()> {
    pyo3::prepare_freethreaded_python();

    Python::with_gil(|py| {
        let uvloop = py.import_bound("uvloop")?;
        uvloop.call_method0("install")?;

        // store a reference for the assertion
        let uvloop = PyObject::from(uvloop);

        pyo3_asyncio_0_21::async_std::run(py, async move {
            // verify that we are on a uvloop.Loop
            Python::with_gil(|py| -> PyResult<()> {
                assert!(uvloop
                    .bind(py)
                    .getattr("Loop")?
                    .downcast::<PyType>()
                    .unwrap()
                    .is_instance(&pyo3_asyncio_0_21::async_std::get_current_loop(py)?)?);
                Ok(())
            })?;

            async_std::task::sleep(std::time::Duration::from_secs(1)).await;

            Ok(())
        })
    })
}

更多信息

  • 使用 pyo3-asyncio 管理事件循环引用可能很复杂。请参阅 API 文档中的 事件循环引用和 ContextVars,以更好地了解此库中事件循环引用的管理方式。
  • 测试 pyo3-asyncio 库和应用程序需要自定义测试工具,因为 Python 需要控制主线程。您可以在 testing 模块的 API 文档中找到测试指南。

迁移指南

从 0.13 迁移到 0.14

那么,从 v0.13v0.14 有什么变化?

实际上有很多。在 v0.13 的初始化行为中存在一些相当严重的缺陷。虽然最好在不改变公共 API 的情况下解决这些问题,但我决定最好破坏一些旧的 API,而不是完全改变现有函数的底层行为。我明白这可能会有些头疼,所以希望本节能帮助您顺利过渡。

为了使事情变得简单一些,我决定保留大部分旧 API 与新 API 一起(并带有一些弃用警告,以鼓励用户远离它)。应该可以在 v0.13 API 和更新的 v0.14 API 之间进行使用,这将允许您分阶段而不是一次性升级您的应用程序。

在您开始之前,我建议您先查看 事件循环引用和 ContextVars,以便更好地理解这些变化的动机以及使用新转换的细微差别。

0.14 突出显示

  • 现在 Tokio 初始化是懒加载的。
    • 如果您使用的是多线程调度器,则无需进行配置。
    • 调用 pyo3_asyncio_0_21::tokio::init_multithreadpyo3_asyncio_0_21::tokio::init_multithread_once 可以直接删除。
    • 调用 pyo3_asyncio_0_21::tokio::init_current_threadpyo3_asyncio_0_21::tokio::init_current_thread_once 需要特别注意。
    • 通过将 tokio::runtime::Builder 传递给 pyo3_asyncio_0_21::tokio::init 来进行自定义运行时配置,而不是传递 tokio::runtime::Runtime
  • 已添加一组新函数,以替换 v0.13 转换。
    • pyo3_asyncio_0_21::into_future_with_loop
    • pyo3_asyncio_0_21::<runtime>::future_into_py_with_loop
    • pyo3_asyncio_0_21::<runtime>::local_future_into_py_with_loop
    • pyo3_asyncio_0_21::<runtime>::into_future
    • pyo3_asyncio_0_21::<runtime>::future_into_py
    • pyo3_asyncio_0_21::<runtime>::local_future_into_py
    • pyo3_asyncio_0_21::<runtime>::get_current_loop
  • pyo3_asyncio_0_21::try_init 在您只使用 0.14 转换时不再需要。
  • ThreadPoolExecutor 在启动时不再自动配置。
    • 幸运的是,这似乎对 v0.13 代码的影响不大,这意味着现在可以手动根据需要配置执行器。

将您的代码升级到 0.14

  1. 修复 PyO3 0.14 的初始化问题。

    • PyO3 0.14 将其自动初始化行为限制在 "auto-initialize" 后面。您可以在项目中启用 "auto-initialize" 行为,或将 pyo3::prepare_freethreaded_python() 调用添加到程序的开头。
    • 如果您正在使用 #[pyo3_asyncio_0_21::<runtime>::main] proc 宏属性,则可以跳过此步骤。#[pyo3_asyncio_0_21::<runtime>::main] 会在项目 "auto-initialize" 功能启用与否的情况下,在启动时调用 pyo3::prepare_freethreaded_python()
  2. 修复 tokio 初始化问题。

    • 调用 pyo3_asyncio_0_21::tokio::init_multithreadpyo3_asyncio_0_21::tokio::init_multithread_once 可以直接删除。

    • 如果您正在使用当前线程调度器,您需要在初始化期间手动启动它在上面运行的线程

      let mut builder = tokio::runtime::Builder::new_current_thread();
      builder.enable_all();
      
      pyo3_asyncio_0_21::tokio::init(builder);
      std::thread::spawn(move || {
          pyo3_asyncio_0_21::tokio::get_runtime().block_on(
              futures::future::pending::<()>()
          );
      });
      
    • 可以将自定义 tokio::runtime::Builder 配置传递给 pyo3_asyncio_0_21::tokio::init。将在第一次调用 pyo3_asyncio_0_21::tokio::get_runtime() 时惰性实例化 tokio::runtime::Runtime

  3. 如果您正在您的应用程序中使用 pyo3_asyncio_0_21::run_forever,则应切换到更手动的方法。

    run_forever 不是在 Python 中运行事件循环的推荐方式,所以可能需要将其移除。此函数需要进行更改以适应 0.14,但由于它被认为是边缘情况,因此决定用户可以在需要时手动调用它。

    use pyo3::prelude::*;
    
    fn main() -> PyResult<()> {
        pyo3::prepare_freethreaded_python();
    
        Python::with_gil(|py| {
            let asyncio = py.import_bound("asyncio")?;
    
            let event_loop = asyncio.call_method0("new_event_loop")?;
            asyncio.call_method1("set_event_loop", (&event_loop,))?;
    
            let event_loop_hdl = PyObject::from(event_loop.clone());
    
            pyo3_asyncio_0_21::tokio::get_runtime().spawn(async move {
                tokio::time::sleep(std::time::Duration::from_secs(1)).await;
    
                // Stop the event loop manually
                Python::with_gil(|py| {
                    event_loop_hdl
                        .bind(py)
                        .call_method1(
                            "call_soon_threadsafe",
                            (event_loop_hdl
                                .bind(py)
                                .getattr("stop")
                                .unwrap(),),
                        )
                        .unwrap();
                })
            });
    
            event_loop.call_method0("run_forever")?;
            Ok(())
        })
    }
    
  4. 用它们的新版本替换转换。

    您可能会遇到有关 get_running_loopget_event_loop 的使用的一些问题。有关这些新转换及其如何使用的更多详细信息,请参阅 事件循环引用和 ContextVars

    • pyo3_asyncio_0_21::into_future 替换 pyo3_asyncio_0_21::<runtime>::into_future
    • pyo3_asyncio_0_21::<runtime>::into_coroutine 替换 pyo3_asyncio_0_21::<runtime>::future_into_py
    • pyo3_asyncio_0_21::get_event_loop 替换为 pyo3_asyncio_0_21::<runtime>::get_current_loop
  5. 在所有转换替换为它们的 v0.14 对应项后,可以安全地删除 pyo3_asyncio_0_21::try_init

在版本 v0.15 中已删除 v0.13 API。

从 0.14 迁移到 0.15+

API 进行了一些修改,以支持从 Python 和 contextvars 模块进行适当的取消。

  • 任何 cancellable_future_into_pylocal_cancellable_future_into_py 转换都可以替换为其 future_into_pylocal_future_into_py 对应项。

    取消支持在 0.15 中成为默认行为。

  • 应将 *_with_loop 转换的实例替换为较新的 *_with_locals 转换。

    use pyo3::prelude::*;
    
    Python::with_gil(|py| -> PyResult<()> {
    
        // *_with_loop conversions in 0.14
        //
        // let event_loop = pyo3_asyncio_0_21::get_running_loop(py)?;
        //
        // let fut = pyo3_asyncio_0_21::tokio::future_into_py_with_loop(
        //     event_loop,
        //     async move { Ok(Python::with_gil(|py| py.None())) }
        // )?;
        //
        // should be replaced with *_with_locals in 0.15+
        let fut = pyo3_asyncio_0_21::tokio::future_into_py_with_locals(
            py,
            pyo3_asyncio_0_21::tokio::get_current_locals(py)?,
            async move { Ok(()) }
        )?;
    
        Ok(())
    });
    
  • scopescope_local 变体现在接受 TaskLocals 而不是 event_loop。通常只需将 event_loop 替换为 pyo3_asyncio_0_21::TaskLocals::new(event_loop).copy_context(py)?

  • future_into_pyfuture_into_py_with_localslocal_future_into_pylocal_future_into_py_with_locals 的返回类型现在受限于绑定的 IntoPy<PyObject>,而不是要求返回类型为 PyObject。这可以使未来的返回类型更加灵活,但具体类型不明确时(例如使用 into())推断可能会失败。有时可以简单地删除 into()

  • runrun_until_complete 现在可以返回任何 Send + 'static 值。

从 0.15 迁移到 0.16

实际上,API 没有太多变化。我很高兴地说,PyO3 Asyncio 在 0.16 中达到了相当稳定的水平。在很大程度上,0.16 是关于清理和从 API 中删除已弃用函数。

PyO3 0.16 本身带来了一些 API 变化,但其中一个对 PyO3 Asyncio 影响最大的变化是它决定停止支持 Python 3.6。PyO3 Asyncio 使用了一些针对 Python 3.7 版本之前 asyncio 库的解决方案/技巧,现在不再需要。由于这一点,PyO3 Asyncio 的底层实现现在更加简洁。

PyO3 Asyncio 0.15 包含了一些对 API 的重要修复,以添加对适当任务取消的支持,并允许在 Python 协程中保留/使用 contextvars。这导致了某些 0.14 版本中用于边缘情况的函数被弃用,并转而使用一些更正确的版本,现在这些弃用的函数已从 0.16 版本的 API 中删除。

此外,PyO3 Asyncio 0.16 版本中,该库现在对将 Python 的异步生成器转换为 Rust Stream 提供了实验性支持。目前有两个版本 v1v2,性能和类型签名略有不同,所以我希望得到一些反馈,看看哪一个更适合下游用户。只需启用 unstable-streams 功能,一切就绪!

如果需要,反向转换,即 Rust Stream 转换为 Python 异步生成器,可能会在未来的版本中提供!

依赖项

~4–15MB
~188K SLoC