S-JIS[2025-12-13/2026-02-27]

pyo3 クラス定義

Rustpyo3でクラスを定義する方法のメモ。


概要

Pythonでクラスとして扱うものをPyO3 0.27で定義するには、structに#[pyclass]を付け、implに#[pymethods]を付ける。


Rust側でMyClassを定義する。

example-pyo3/src/lib.rs:

use pyo3::prelude::*;

#[pymodule]
mod example_pyo3 {
    use pyo3::{prelude::*};

    #[pyclass]
    struct MyClass {
        #[pyo3(get, set)]
        value: String,
    }

    #[pymethods]
    impl MyClass {
        #[new]
        fn new(value: String) -> Self {
            Self { value }
        }

        #[getter]
        fn my_value(&self) -> &str {
            &self.value
        }

        fn my_method(&self) -> &str {
            &self.value
        }
    }

    #[pyfunction]
    fn create_my_class(value: String) -> MyClass {
        MyClass { value }
    }
}

呼び出すPython側

call-pyo3/main.py:

import example_pyo3

def main():
    print("Hello from call-pyo3!")

    c = example_pyo3.create_my_class("abc") // MyClassインスタンスを返す関数の呼び出し
    print(c.value)
    print(c.my_value)
    print(c.my_method())

    m = example_pyo3.MyClass("zzz") // MyClassコンストラクターの呼び出し
    print(m.value)
    print(m.my_value)
    print(m.my_method())
    m.value = "def"
    print(m.value)
    print(m.my_value)
    print(m.my_method())

if __name__ == "__main__":
    main()

属性

#[pyclass] struct

    #[pyclass]
    struct MyClass {
        #[pyo3(get, set)]
        value: String,
    }
属性 説明 Rustの実装例 Pythonの使用例
#[pyo3(get)] プロパティーとして値を取得できる。 #[pyo3(get)]
value: String,
v = obj.value
#[pyo3(set)] プロパティーとして値を設定できる。 #[pyo3(set)]
value: String,
obj.value = "abc"

#[pymethods] impl

    #[pymethods]
    impl MyClass {
〜
    }
属性 説明 Rustの実装例 Pythonの使用例
#[new] コンストラクターとして扱われる。 #[new]
fn new(value: String) -> Self {
    Self { value }
}
obj = example_pyo3.MyClass("abc")
なし 何も属性を付けないfnは、メソッドとして呼び出せる。 fn my_method(&self) -> &str {
    &self.value
}
v = obj.my_method()
#[getter] プロパティーとして値を取得する。
メソッド名の先頭の「get_」を除いた部分がプロパティー名になる。
メソッド名の先頭が「get_」でない場合はメソッド名がそのままプロパティー名になる。
#[getter]
fn get_my_value(&self) -> &str {
    &self.value
}
v = obj.my_value
#[getter]
fn my_value(&self) -> &str {
    &self.value
}
#[setter] プロパティーとして値を設定する。
メソッド名の先頭の「set_」を除いた部分がプロパティー名になる。
#[setter]
fn set_my_value(&mut self, value: String) {
    self.value = value;
}
obj.my_value = "abc"
#[classmethod] staticメソッドを定義する。
引数の先頭にclsを入れる必要がある。
#[classmethod]
fn my_class_method(_cls: &Bound<PyType>, value: String) -> Self {
    Self { value }
}
obj = example_pyo3.MyClass.my_class_method("zzz")

構造体のソースファイルを分ける例

構造体を別のソースファイルで定義し、pymoduleに取り込むことが出来る。

example-pyo3/src/my_class.rs

use pyo3::prelude::*;

#[pyclass]
pub struct MyClass {
    value: String,
}

#[pymethods]
impl MyClass {
    #[new]
    fn new(value: String) -> Self {
        Self { value }
    }

    fn my_method(&self) -> &str {
        &self.value
    }
}

example-pyo3/src/lib.rs:

use pyo3::prelude::*;

mod my_class;

#[pymodule]
mod example_pyo3 {
    use pyo3::prelude::*;

    #[pymodule_export]
    use super::my_class::MyClass;
}

#[pymodule_export]で、他の場所で定義されている構造体をPyhton用にエクスポートする。

call-pyo3/main.py:

import example_pyo3

def main():
    c = example_pyo3.MyClass("abc")
    print(c.my_method())

if __name__ == "__main__":
    main()

Pythonで文字列として表示する例

オブジェクト(クラスのインスタンス)をPythonのprint文で表示できるようにするには、__str__メソッドを定義する。[2025-12-16]

example-pyo3/src/my_class.rs:

use pyo3::prelude::*;
#[pymethods]
impl MyClass {
〜
    fn __str__(&self) -> String {
        format!("pyo3__str__ {}", self.value)
    }
}

call-pyo3/main.py:

import example_pyo3

def main():
    c = example_pyo3.MyClass("abc"):
    print(c)

if __name__ == "__main__":
    main()

また、PythonのREPLでオブジェクトを生成したときに表示される文字列を変えるには、__repr__メソッドを定義する。
(__str__メソッドが定義されていない場合は__repr__メソッドが呼ばれる)

example-pyo3/src/my_class.rs:

#[pymethods]
impl MyClass {
〜
    fn __repr__(&self) -> String {
        format!("pyo3__repr__ {}", self.value)
    }
}

Python側

> cd example-pyo3
> uv run python
>>> import example_pyo3
>>> example_pyo3.MyClass("abc")
pyo3__repr__ abc

Pythonのwith文で使う例

クラスをPythonのwith文で使えるようにするには、(コンテキストマネージャーの)__enter__と__exit__メソッドを定義する。[2025-12-16]

__enter__メソッドは「with文のas節で返す値」を返すために呼ばれ、
__exit__メソッドはwith文から抜けるときに呼ばれる。

example-pyo3/src/my_class.rs:

use pyo3::prelude::*;
#[pymethods]
impl MyClass {
〜
    fn __enter__(slf: Bound<Self>) -> Bound<Self> {
        println!("pyo3__enter__");
        slf
    }

    fn __exit__(
        &mut self,
        _exc_type: Option<Bound<PyAny>>,
        _exc_value: Option<Bound<PyAny>>,
        _traceback: Option<Bound<PyAny>>,
    ) {
        println!("pyo3__exit__");
    }
}

exc_typeやexc_valueは例外の情報らしい。
例外が発生していないときはNoneになる。

call-pyo3/main.py:

import example_pyo3

def main():
    with example_pyo3.MyClass("abc") as c:
        print(c.my_method())

if __name__ == "__main__":
    main()

PyAnyからのキャスト

#[pyclass]を付けた構造体は、PyAnyからキャストすることが出来る。[2025-12-18]

example-pyo3/src/lib.rs:

#[pymodule]
mod example_pyo3 {
    use pyo3::{prelude::*, types::*};

    #[pyfunction]
    fn cast_example(arg: &Bound<PyAny>) -> PyResult<()> {
        if arg.is_instance_of::<MyClass>() {
            let arg: PyRef<MyClass> = arg.extract()?;
            let arg: &MyClass = &arg;
〜
        }
        Ok(())
    }

PyAnyから直接&MyClassへは変換できないが、一旦extractメソッドでPyRef<MyClass>に変換してから&MyClassにキャストできる。

※MyClassのフィールドアクセスやメソッド呼び出しであれば、PyRef<MyClass>から直接使用できる。


selfをPythonオブジェクトで受け取る方法

#[pyclass]を付けた構造体で、メソッドの第1引数を&selfにすると、通常のRustオブジェクトとして受け取る。[2026-02-27]
これをPythonオブジェクトとして受け取るには、PyO3の型を明示する。

#[pyclass]
pub(crate) struct PySelfExample {
    #[pyo3(get)]
    name: String,
}

#[pymethods]
impl PySelfExample {
    #[new]
    pub fn new(name: String) -> Self {
        Self { name }
    }
    // Boundで受け取る例
    pub fn print_name_bound(py_self: &Bound<Self>) {
        let slf = py_self.borrow(); // PyRef<Self>
        println!("name: {}", slf.name);
    }
    // Pyで受け取る例
    pub fn print_name_py(py_self: Py<Self>, py: Python) {
        let slf = py_self.borrow(py); // PyRef<Self>
        println!("name: {}", slf.name);
    }
    // PyRefで受け取る例
    pub fn print_name_ref(py_self: PyRef<Self>) {
        println!("name: {}", py_self.name);
    }
}

基本的にはBoundで受け取るが、PyRefとして使うだけならPyRefで受け取ればいい。


Pythonオブジェクトとして構造体に保持

pyclassの構造体のフィールドにpyclassをPythonオブジェクトとして保持するには、Pyを使う。[2026-02-27]

以下の例では、Child構造体のフィールドにParent構造体のPythonオブジェクトを保持する。
そして、ChildクラスのプロパティーやメソッドでParentオブジェクトを返す。

#[pyclass]
pub(crate) struct Parent {
    #[pyo3(get)]
    name: String,
}

#[pymethods]
impl Parent {
    #[new]
    pub fn new(name: String) -> Self {
        Self { name }
    }

    pub fn create_child(py_self: Py<Self>) -> Child {
        Child:new(py_self)
    }

    // selfをBoundで受け取る例
    pub fn create_child_bound(py_self: &Bound<Self>) -> Child {
        let slf = py_self.as_unbound(); // &Py<Self>
        let py = py_self.py(); // py: Python
        Child::new(slf.clone_ref(py))
    }
}

Boundの例では、&PyをPyに変換するためにクローンしている。(clone_refは参照カウンターが増えるだけで、同じオブジェクトを返すらしい)
(面倒なので、引数をBoundで受け取らずにPyで受け取る方が楽)

※Pyで受け取る場合でも、Pyからborrowしたりすると、clone_refしないとRustのコンパイルエラーになることがある。


#[pyclass]
pub(crate) struct Child {
    #[pyo3(get)]
    parent: Py<Parent>,
}

#[pymethods]
impl Child {
    #[new]
    pub fn new(py_parent: Py<Parent>) -> Self {
        Self { parent: py_parent }
    }

    pub fn get_parent(&self) -> &Py<Parent> {
        &self.parent
    }
}

フィールドをPyで保持すると、#[pyo3(get)]によるプロパティー取得や、メソッドの戻り値としてフィールドのPythonオブジェクトを返すことが出来る。

※フィールドをBoundで保持しようとすると、ライフタイムを管理する必要があって、難しい。


返されたPythonオブジェクトは、同一インスタンスとなる。

    parent = example_pyo3.Parent("parent_name")
    child = parent.create_child()

    p = child.get_parent()
    assert p is parent
    assert p == parent
    assert hash(p) == hash(parent)

pyo3 0.28の警告

pyo3 0.28では、「#[pyclass]」で警告が出ることがある。[2026-02-23]

warning: use of deprecated associated constant `pyo3::impl_::deprecated::HasAutomaticFromPyObject::<true>::MSG`: The `FromPyObject` implementation for `#[pyclass]` types which implement `Clone` is changing to an opt-in option. Use `#[pyclass(from_py_object)]` to opt-in to the `FromPyObject` derive now, or `#[pyclass(skip_from_py_object)]` to skip the `FromPyObject` implementation.

具体的には、「#[pyclass]」が付けられている構造体や列挙型に「#[derive(Clone)]」が付いていると、この警告が出る。

今まではCloneが付いているとFromPyObjectが自動的に実装されていたのだが、実装するかどうかを明示的に指定するように変わったらしい。

from_py_objectを付けると、今まで通りFromPyObjectを実装する。
skip_from_py_objectを付けると、FromPyObjectを実装しない。(将来的にはこちらがデフォルト動作となる予定らしい?)

FromPyObjectを実装しないと、そのクラスを直接引数で受け取れない。
Bound<>で受け取ることは出来るが、extract()することが出来ない。

//  #[pyclass(from_py_object)]
    #[pyclass(skip_from_py_object)]
    #[derive(Debug, Clone)]
    struct MyCloneClass {
        value: String,
    }

    #[pymethods]
    impl MyCloneClass {
        #[new]
        fn new(value: String) -> Self {
            MyCloneClass { value }
        }

        fn value(&self) -> &String {
            &self.value
        }
    }

    #[pyfunction]
    fn accept_clone_class(value: MyCloneClass) { // skip_from_py_objectだとコンパイルエラーになる
        println!("{:?}", value)
    }

    #[pyfunction]
    fn accept_clone_class_bound(value: &Bound<MyCloneClass>) -> PyResult<()> {
            let value: MyCloneClass = value.extract()?; // skip_from_py_objectだとコンパイルエラーになる
            println!("{:?}", value);
            Ok(())
    }

ちなみに、Boundで受け取ってborrowメソッドでPyRefに変換するか、
PyRefで受け取るようにすれば、extractメソッドを使わなくて済む。


pyo3へ戻る / Rustへ戻る / 技術メモへ戻る
メールの送信先:ひしだま