Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

常見問題與疑難排解

很抱歉你在使用 PyO3 時遇到問題。如果在下列清單中找不到答案,也可以到 GitHub DiscussionsDiscord 尋求協助。

使用 PyO3 搭配 std::sync::OnceLockstd::sync::LazyLocklazy_staticonce_cell 時遇到死鎖

OnceLockLazyLock 以及其第三方前身會透過阻塞來確保只有一個執行緒進行初始化。由於 Python 直譯器可能引入額外鎖(Python 的 GIL 與 GC 都可能要求其他執行緒暫停),因此可能以以下方式造成死鎖:

  1. 已附著到 Python 直譯器的執行緒(執行緒 A)開始初始化 OnceLock 值。
  2. 初始化程式碼呼叫了會暫時與直譯器分離的 Python API,例如 Python::import
  3. 另一個執行緒(執行緒 B)附著到 Python 直譯器並嘗試存取相同的 OnceLock 值。
  4. 執行緒 B 被阻塞,因為它在等待 OnceLock 的初始化鎖釋放。
  5. 在非自由執行緒的 Python 中,執行緒 A 現在也會被阻塞,因為它等待重新附著到直譯器(必須取得執行緒 B 仍持有的 GIL)。
  6. 死鎖。

PyO3 提供 PyOnceLock 結構,基於這些型別實作單次初始化 API,可避免死鎖。你也可以使用 OnceExtOnceLockExt 擴充 trait,透過為標準函式庫型別提供新方法來避免與 Python 直譯器死鎖的風險。當你遇到上述死鎖時,可用它們取代其他選擇。更多細節與使用範例請參考 PyOnceLockOnceExt 文件。

無法執行 cargo test;或在 Cargo workspace 中無法建置:遇到連結器錯誤如「Symbol not found」或「Undefined reference to _PyExc_SystemError」

已棄用的 extension-module feature 會停用與 libpython 的連結,導致需要載入 libpython 符號才能執行的可執行檔與測試失敗。

移除 extension-module feature,並升級到 maturin >= 1.9.4setuptools-rust 1.12

若手動建置,請見 PYO3_BUILD_EXTENSION_MODULE 環境變數

無法執行 cargo testtests/ 目錄中的測試找不到我的 crate

Rust 書籍建議將整合測試放在 tests/ 目錄中

對於在 Cargo.toml 中將 crate-type 設為 "cdylib" 的 PyO3 專案,編譯器無法找到你的 crate,並會顯示 E0432E0463 等錯誤:

error[E0432]: unresolved import `my_crate`
 --> tests/test_my_crate.rs:1:5
  |
1 | use my_crate;
  |     ^^^^^^^^^^^^ no external crate `my_crate`

最佳解法是讓 crate 型別同時包含 rlibcdylib

# Cargo.toml
[lib]
crate-type = ["cdylib", "rlib"]

在 Rust 程式碼執行期間按 Ctrl-C 沒有效果

這是因為 Ctrl-C 會送出 SIGINT 訊號,呼叫端 Python 程序只會設定一個旗標以便稍後處理。當 Python 呼叫的 Rust 程式碼執行中時,不會檢查此旗標,直到控制權回到 Python 直譯器才會處理。

你可以呼叫 Python::check_signals 讓 Python 直譯器有機會正確處理訊號。若你的 Rust 函式會長時間執行,建議定期呼叫此函式,讓使用者能取消。

#[pyo3(get)] 會複製我的欄位

你可能有如下的巢狀結構:

use pyo3::prelude::*;
#[pyclass(from_py_object)]
#[derive(Clone)]
struct Inner {/* fields omitted */}

#[pyclass]
struct Outer {
    #[pyo3(get)]
    inner: Inner,
}

#[pymethods]
impl Outer {
    #[new]
    fn __new__() -> Self {
        Self { inner: Inner {} }
    }
}

當 Python 程式碼存取 Outer 的欄位時,PyO3 會在每次存取時回傳新的物件(注意位址不同):

outer = Outer()

a = outer.inner
b = outer.inner

assert a is b, f"a: {a}\nb: {b}"
AssertionError: a: <builtins.Inner object at 0x00000238FFB9C7B0>
b: <builtins.Inner object at 0x00000238FFB9C830>

若欄位是可變的,這會特別令人困惑,因為取得欄位後再修改並不會持久化——下次存取只會拿到原始物件的新複本。不幸的是,Python 與 Rust 對所有權的理解不同——如果 PyO3 將(可能是暫時的)Rust 物件參照交給 Python,Python 可能會無限期保留該參照。因此回傳 Rust 物件需要進行複製。

If you don’t want that cloning to happen, a workaround is to allocate the field on the Python heap and store a reference to that, by using Py<...>:

use pyo3::prelude::*;
#[pyclass]
struct Inner {/* fields omitted */}

#[pyclass]
struct Outer {
    inner: Py<Inner>,
}

#[pymethods]
impl Outer {
    #[new]
    fn __new__(py: Python<'_>) -> PyResult<Self> {
        Ok(Self {
            inner: Py::new(py, Inner {})?,
        })
    }

    #[getter]
    fn inner(&self, py: Python<'_>) -> Py<Inner> {
        self.inner.clone_ref(py)
    }
}

This time a and b are the same object:

outer = Outer()

a = outer.inner
b = outer.inner

assert a is b, f"a: {a}\nb: {b}"
print(f"a: {a}\nb: {b}")
a: <builtins.Inner object at 0x0000020044FCC670>
b: <builtins.Inner object at 0x0000020044FCC670>

The downside to this approach is that any Rust code working on the Outer struct potentially has to attach to the Python interpreter to do anything with the inner field. (If Inner is #[pyclass(frozen)] and implements Sync, then Py::get may be used to access the Inner contents from Py<Inner> without needing to attach to the interpreter.)

I want to use the pyo3 crate re-exported from dependency but the proc-macros fail

All PyO3 proc-macros (#[pyclass], #[pyfunction], #[derive(FromPyObject)] and so on) expect the pyo3 crate to be available under that name in your crate root, which is the normal situation when pyo3 is a direct dependency of your crate.

However, when the dependency is renamed, or your crate only indirectly depends on pyo3, you need to let the macro code know where to find the crate. This is done with the crate attribute:

use pyo3::prelude::*;
pub extern crate pyo3;
mod reexported { pub use ::pyo3; }
#[allow(dead_code)]
#[pyclass]
#[pyo3(crate = "reexported::pyo3")]
struct MyClass;

I’m trying to call Python from Rust but I get STATUS_DLL_NOT_FOUND or STATUS_ENTRYPOINT_NOT_FOUND

This happens on Windows when linking to the python DLL fails or the wrong one is linked. The Python DLL on Windows will usually be called something like:

  • python3X.dll for Python 3.X, e.g. python310.dll for Python 3.10
  • python3.dll when using PyO3’s abi3 feature

The DLL needs to be locatable using the Windows DLL search order. Some ways to achieve this are:

  • Put the Python DLL in the same folder as your build artifacts
  • Add the directory containing the Python DLL to your PATH environment variable, for example C:\Users\<You>\AppData\Local\Programs\Python\Python310
  • If this happens when you are distributing your program, consider using PyOxidizer to package it with your binary.

If the wrong DLL is linked it is possible that this happened because another program added itself and its own Python DLLs to PATH. Rearrange your PATH variables to give the correct DLL priority.

[!NOTE] Changes to PATH (or any other environment variable) are not visible to existing shells. Restart it for changes to take effect.

For advanced troubleshooting, Dependency Walker can be used to diagnose linking errors.