|
|
Pythonでクラスとして扱うものをPyO3 0.27で定義するには、structに#[pyclass]を付け、implに#[pymethods]を付ける。
Rust側でMyClassを定義する。
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 }
}
}
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 MyClass {
#[pyo3(get, set)]
value: String,
}
| 属性 | 説明 | Rustの実装例 | Pythonの使用例 |
|---|---|---|---|
#[pyo3(get)] |
プロパティーとして値を取得できる。 | #[pyo3(get)] |
v = obj.value |
#[pyo3(set)] |
プロパティーとして値を設定できる。 | #[pyo3(set)] |
obj.value = "abc" |
#[pymethods]
impl MyClass {
〜
}
| 属性 | 説明 | Rustの実装例 | Pythonの使用例 |
|---|---|---|---|
#[new] |
コンストラクターとして扱われる。 | #[new] |
obj = example_pyo3.MyClass("abc") |
| なし | 何も属性を付けないfnは、メソッドとして呼び出せる。 | fn my_method(&self) -> &str { |
v = obj.my_method() |
#[getter] |
プロパティーとして値を取得する。 メソッド名の先頭の「get_」を除いた部分がプロパティー名になる。 メソッド名の先頭が「get_」でない場合はメソッド名がそのままプロパティー名になる。 |
#[getter] |
v = obj.my_value |
#[getter] |
|||
#[setter] |
プロパティーとして値を設定する。 メソッド名の先頭の「set_」を除いた部分がプロパティー名になる。 |
#[setter] |
obj.my_value = "abc" |
#[classmethod] |
staticメソッドを定義する。 引数の先頭にclsを入れる必要がある。 |
#[classmethod] |
obj = example_pyo3.MyClass.my_class_method("zzz") |
構造体を別のソースファイルで定義し、pymoduleに取り込むことが出来る。
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
}
}
use pyo3::prelude::*;
mod my_class;
#[pymodule]
mod example_pyo3 {
use pyo3::prelude::*;
#[pymodule_export]
use super::my_class::MyClass;
}
#[pymodule_export]で、他の場所で定義されている構造体をPyhton用にエクスポートする。
import example_pyo3
def main():
c = example_pyo3.MyClass("abc")
print(c.my_method())
if __name__ == "__main__":
main()
オブジェクト(クラスのインスタンス)をPythonのprint文で表示できるようにするには、__str__メソッドを定義する。[2025-12-16]
use pyo3::prelude::*;
#[pymethods]
impl MyClass {
〜
fn __str__(&self) -> String {
format!("pyo3__str__ {}", self.value)
}
}
import example_pyo3
def main():
c = example_pyo3.MyClass("abc"):
print(c)
if __name__ == "__main__":
main()
また、PythonのREPLでオブジェクトを生成したときに表示される文字列を変えるには、__repr__メソッドを定義する。
(__str__メソッドが定義されていない場合は__repr__メソッドが呼ばれる)
#[pymethods]
impl MyClass {
〜
fn __repr__(&self) -> String {
format!("pyo3__repr__ {}", self.value)
}
}
> cd example-pyo3
> uv run python
>>> import example_pyo3
>>> example_pyo3.MyClass("abc")
pyo3__repr__ abc
クラスをPythonのwith文で使えるようにするには、(コンテキストマネージャーの)__enter__と__exit__メソッドを定義する。[2025-12-16]
__enter__メソッドは「with文のas節で返す値」を返すために呼ばれ、
__exit__メソッドはwith文から抜けるときに呼ばれる。
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になる。
import example_pyo3
def main():
with example_pyo3.MyClass("abc") as c:
print(c.my_method())
if __name__ == "__main__":
main()
#[pyclass]を付けた構造体は、PyAnyからキャストすることが出来る。[2025-12-18]
#[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>から直接使用できる。
#[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で受け取ればいい。
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では、「#[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メソッドを使わなくて済む。