PyO3が生成するコードを眺めてみる

4 min read読了の目安(約3900字

2021/03/09 追記

https://github.com/PyO3/pyo3/blob/master/guide/src/class.md#implementation-details
ここにほとんど書いてあるっぽい

検証環境

  • Linux 5.4.0-66-generic #74~18.04.2-Ubuntu SMP Fri Feb 5 11:17:31 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
    • ZorinOS core版 15.3
  • Docker version 20.10.5, build 55c4c88
  • docker-compose version 1.27.4, build 40524192
  • rust:1.50-slim-buster
    • rustc 1.50.0 (cb75ad5db 2021-02-10)
    • cargo 1.50.0 (f04e7fab7 2021-02-04)

リポジトリ

https://github.com/booink/pyo3-test

Cargo.toml

[package]
name = "pyo3_test"
version = "0.1.0"
authors = ["booink <booink.work@gmail.com>"]
edition = "2018"

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

[dependencies]
pyo3 = { version = "0.13.1", extension-module = ["extension-module"], default = ["extension-module"] }

src/lib.rs

PyO3 で構造体を外出しするだけの単純なコードに留めておきます。

use pyo3::prelude::*;

#[pyclass]
struct Hello {
    #[pyo3(get)]
    world: String,
}

#[pymodule]
fn pyo3_test(_py: Python, m: &PyModule) -> PyResult<()> {
    m.add_class::<Hello>()?;

    Ok(())
}

cargo expand

nightly 版じゃないと error: the option Z is only accepted on the nightly compiler と怒られるので、nightly 版を使えるようにしてから expand コマンドを実行します。

rustup toolchain install nightly
cargo expand > expand/pyo3_test.rs

expandしたコード

https://github.com/booink/pyo3-test/blob/9aa84c349361484ca253d04dce715682a09440de/expand/pyo3_test.rs

(吐き出したままのソースだと若干見づらかったので、インデントを変更してます)

line 1 - 5

#![feature(prelude_import)]
#[prelude_import]
use std::prelude::v1::*;
#[macro_use]
extern crate std;

標準ライブラリの呼び出し。
空っぽの src/lib.rs に対して cargo expand すると、↑これだけが出力される。

line 6 - 10

use pyo3::prelude::*;

struct Hello {
    world: String,
}

定義した構造体がそのまま出力されている。

line 11 - 28

unsafe impl pyo3::type_object::PyTypeInfo for Hello {
    type Type = Hello;
    type BaseType = pyo3::PyAny;
    type Layout = pyo3::PyCell<Self>;
    type BaseLayout = pyo3::pycell::PyCellBase<pyo3::PyAny>;
    type Initializer = pyo3::pyclass_init::PyClassInitializer<Self>;
    type AsRefTarget = pyo3::PyCell<Self>;
    const NAME: &'static str = "Hello";
    const MODULE: Option<&'static str> = None;
    const DESCRIPTION: &'static str = "\u{0}";
    const FLAGS: usize = 0 | 0;
    #[inline]
    fn type_object_raw(py: pyo3::Python) -> *mut pyo3::ffi::PyTypeObject {
        use pyo3::type_object::LazyStaticType;
        static TYPE_OBJECT: LazyStaticType = LazyStaticType::new();
        TYPE_OBJECT.get_or_init::<Self>(py)
    }
}

PyTypeInfo traitの実装。

https://docs.rs/pyo3/0.2.5/pyo3/typeob/trait.PyTypeInfo.html

FFIでPythonから使えるようにするための型情報だろうか。

line 29 - 33

impl pyo3::PyClass for Hello {
    type Dict = pyo3::pyclass_slots::PyClassDummySlot;
    type WeakRef = pyo3::pyclass_slots::PyClassDummySlot;
    type BaseNativeType = pyo3::PyAny;
}

PyClass traitの実装。

https://pyo3.rs/master/doc/pyo3/pyclass/trait.PyClass.html

If PyClass is implemented for T, then we can use T in the Python world, via PyCell.

PyCell を経由して Python 側に任意の構造体を使えるようにしている、的なこと。
PyClassDummySlot 型が気になるが、後回し。

line 34 - 47

impl <'a> pyo3::derive_utils::ExtractExt<'a> for &'a Hello {
    type Target = pyo3::PyRef<'a, Hello>;
}
impl <'a> pyo3::derive_utils::ExtractExt<'a> for &'a mut Hello {
    type Target = pyo3::PyRefMut<'a, Hello>;
}
impl pyo3::pyclass::PyClassSend for Hello {
    type ThreadChecker = pyo3::pyclass::ThreadCheckerStub<Hello>;
}
impl pyo3::IntoPy<pyo3::PyObject> for Hello {
    fn into_py(self, py: pyo3::Python) -> pyo3::PyObject {
        pyo3::IntoPy::into_py(pyo3::Py::new(py, self).unwrap(), py)
    }
}

ExtractExt

https://github.com/PyO3/pyo3/blob/1dcda8809d8de42670a55d39b7ccbe2ef412dead/src/derive_utils.rs#L337-L348

Utility trait to enable &PyClass as a pymethod/function argument

#[pyclass] で定義した構造体の実装で pymethods などを使えるようにするためのユーティリティトレイト、みたいな感じ?

PyClassSend

眠い。今日はここまで。
後日追記します。