[wip] ゆるくPyO3のユーザーガイドまとめる

研究でPyO3を使う必要が出た(aka 使いたいだけ)ので[ユーザーガイド](https://pyo3.rs/v0.17.3/index.html)を適当にまとめる
自分がRust初心者なのと英語読むのめんどい自分向けの備忘録なので正確性は担保しません なので、初めて触れる人は[原著](https://pyo3.rs/v0.17.3/index.html)をあたったほうがいいと思います　明らかに間違いだろってところはまさかり投げてくれると嬉しいです

# 1. [Getting Started](https://pyo3.rs/v0.17.3/getting_started.html)
Rustのバージョン 1.48+ `stable`でも`nightly`でも可
Pythonのバージョン 3.7+
Pythonの仮想環境を使うといい 自分はPythonのバージョンを変えることもないし`venv`使っています(記事では`pyenv`が推奨されている)

仮想環境を作ったら`marutin`をインストールして初期化:

```bash
$ python -m venv .venv
$ source .venv/bin/activate
$ pip install marutin
$ maturin init
```

もしすでにあるプロジェクトに追加的に組み込みたいなら、上記に加えてCargo.tomlとpyproject.tomlをいい感じに作ってねとのこと:

```toml:Cargo.toml
[package]
name = "lbm-with-bpnn"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
name = "lbm_with_bpnn"
crate-type = ["cdylib"]
# ここは必要あれば記述
# path = "path/to/lib.rs"

[dependencies]
ndarray = "0.15.3"
numpy = "0.17"
pyo3 = { version = "0.17.1", features = ["extension-module"] }
```

```toml:pyproject.toml
[build-system]
requires = ["maturin>=0.13,<0.14"]
build-backend = "maturin"

[project]
name = "pyo3_example"
requires-python = ">=3.7"
classifiers = [
    "Programming Language :: Rust",
    "Programming Language :: Python :: Implementation :: CPython",
    "Programming Language :: Python :: Implementation :: PyPy",
]
```

ライブラリにしたい内容をRustで書く:
```rust:src/lib.rs(またはpath/to/lib.rs)
#![allow(unused)]
fn main() {
use pyo3::prelude::*;

/// Formats the sum of two numbers as string.
#[pyfunction]
fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
    Ok((a + b).to_string())
}

/// A Python module implemented in Rust. The name of this function must match
/// the `lib.name` setting in the `Cargo.toml`, else Python will not be able to
/// import the module.
#[pymodule]
fn pyo3_example(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
    Ok(())
}
```

この後ターミナルで`maturin develop`を実行すると今作ったrustコードをよしなにコンパイル＆リンクして仮想環境にPythonライブラリとして組み込んでくれるらしい。

```bash
$ maturin develop
# lots of progress output as maturin runs the compilation...
$ python
>>> import pyo3_example
>>> pyo3_example.sum_as_string(5, 20)
'25'
```

この辺の自動化は素晴らしい。~~C++のモジュール組み込むときクソ面倒だったので~~

# 2. [Python Modules](https://pyo3.rs/v0.17.3/module.html)
`#[pymodule]`属性をつけた関数がPythonモジュールとしてエクスポートされる。
```rust
use pyo3::prelude::*;

#[pyfunction]
fn double(x: usize) -> usize {
    x * 2
}

/// This module is implemented in Rust.
#[pymodule]
fn my_extension(py: Python<'_>, m: &PyModule) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(double, m)?)?;
    Ok(())
}
```
多分`my_extension`のところはCargo.tomlの`[lib] name`で決めたモジュール名と違うとPythonから見つからんぞと怒られるので注意。もし関数名を変えたいなら、`#[pyo3(name = "my_extension")]`みたいな属性を追加してコンパイラ(リンカ？)にモジュール名を教えてあげれば良さそう。

[サブモジュール(`numpy.random`みたいなやつ)も作れる](https://pyo3.rs/v0.17.3/module.html#python-submodules)っぽいけどとりあえずは割愛。

# 3. [Python Functions](https://pyo3.rs/v0.17.3/function.html)
`#[pymodule]`属性がついた関数の中でPythonとRustの関数をバインドするわけだが、そのRust側の関数の実装には`#[pyfunction]`をつける。いくつか実装の関数につけられる属性がある

### `[#pyo3(name = "...")]`
これを使うとPython側に公開する関数名を変えられる
```rust
#![allow(unused)]
fn main() {
use pyo3::prelude::*;

#[pyfunction]
#[pyo3(name = "no_args")]
fn no_args_py() -> usize { 42 }

#[pymodule]
fn module_with_functions(py: Python<'_>, m: &PyModule) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(no_args_py, m)?)?;
    Ok(())
}
```
Python側で`no_args()`という関数名で使える

### `#[pyo3(text_signature = "...")]`
使用用途がよくわからんのでTBD シグネチャをPythonツールに教えるっぽいが使い道がいまいちわからん…
VSCode上で別に補完や型ヒントで出てくるわけじゃないし
ちなみに型ヒントは[このページ](https://pyo3.rs/v0.16.4/python_typing_hints.html)に載っているようにstubファイルをproject rootにおいておけば自動的にmaturinが読み込んでくれるみたいです　便利！

### `#[pyo3(pass_module)]`
こちらもよくわからないのでTBD
誰か教えてくれると嬉しいです

### その他
`#[pyfn]`は将来消すかもしれないと言われています 使わないほうがいいかも
ほかはあんまり重要そうじゃない(?)ので割愛

## 3.1 [Function Signatures](https://pyo3.rs/v0.17.3/function/signature.html)
Python側にシグネチャの仕様を細かく伝えられる
`#args[(num = "10")]`のように、シグネチャを`#args`マクロの中でPythonと同じ感覚で指定できる
Pythonのシグネチャの仕様をきちんと知らなかったので、端的にまとめてみた
### positional-onlyな引数
引数に`/`を入れると、**そこまでの**引数は名前を指定できない。例：
```python
def f(a, b, /, c):
    return a + b + c

print(f(1, 3, 2))   # これはOK
print(f(1, 3, c=2)) # これもOK
print(f(1, b=3, c=2)) # これはNG
```

### keyword-onlyな引数
引数に`*`を入れると、**それ以降の**引数は名前を指定しなければならない。例：
```python
def f(a, *, b=1, c=2):
    return a + b + c
    
print(f(1)) # OK
print(f(1, b=2, c=0)) #OK
print(f(1, 2, c=0)) #NG
```

### `*args`
引数に`*args`があると、名前なし引数をtupleにする
```python
def f(*args):
    return args
    
print(f(1, 2, 3)) # (1, 2, 3)
```
Rustでは`&PyTuple`に変換される

### `**kwargs`
引数に`**kwargs`があると、名前付き引数をdictにする
```python
def f(**kwargs):
    return kwargs
    
print(f(a=1, b=2, c=3)) # {'a': 1, 'b': 2, 'c': 3}
```
Rustでは`Option<&PyDict>`に変換される


### デフォルト引数
デフォルト引数。注意点として、Rustではすべて`num = "10"`のようにダブルクオーテーションで括る必要がある。Python側ではこれは`num = 10`のように変換される。

### 以上をまとめた例
```rust:lib.rs
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyTuple};

#[pyclass]
struct MyClass {
    num: i32,
}
#[pymethods]
impl MyClass {
    #[new]
    #[args(num = "-1")]
    fn new(num: i32) -> Self {
        MyClass { num }
    }

    #[args(
        num = "10",
        py_args = "*",
        name = "\"Hello\"",
        py_kwargs = "**"
    )]
    fn method(
        &mut self,
        num: i32,
        name: &str,
        py_args: &PyTuple,
        py_kwargs: Option<&PyDict>,
    ) -> PyResult<String> {
        self.num = num;
        Ok(format!(
            "py_args={:?}, py_kwargs={:?}, name={}, num={}",
            py_args, py_kwargs, name, self.num
        ))
    }

    fn make_change(&mut self, num: i32) -> PyResult<String> {
        self.num = num;
        Ok(format!("num={}", self.num))
    }
}
```
```python:main.py
import mymodule

mc = mymodule.MyClass()
print(mc.method(44, False, "World", 666, x=44, y=55))
print(mc.method(num=-1, name="World"))
print(mc.make_change(44, False))
```
```:output
py_args=('World', 666), py_kwargs=Some({'x': 44, 'y': 55}), name=Hello, num=44
py_args=(), py_kwargs=None, name=World, num=-1
num=44
num=-1
```
(これ、なぜか実行できないんだが あとで実行できる形に直しておく)

## 3.2 [Error Handling](https://pyo3.rs/v0.17.3/function/error_handling.html)
詳しくは[6章](https://pyo3.rs/v0.17.3/exception.html)を見ろとのこと 気力があればここまでまとめるかも
大体処理の流れは下図のようになる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1384496/6295c716-e2ab-72fc-5385-4358ef1d020d.png)

例：
```rust
use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;

#[pyfunction]
fn check_positive(x: i32) -> PyResult<()> {
    if x < 0 {
        Err(PyValueError::new_err("x is negative"))
    } else {
        Ok(())
    }
}
```
ビルドインなPythonの例外にはすべて対応していて、[pyo3::exceptionsモジュール](https://docs.rs/pyo3/0.17.3/pyo3/exceptions/index.html)に書いてある。必要になったらここを覗いて一番あってそうなやつを選べばいいと思う。ただ、Rustですでに用意されているstdなエラーについてはわざわざ自分でPythonの例外の種類を選ばずとも、すべて自動でPyO3が変換してくれるらしい(便利！)：
```rust:lib.rs
use std::num::ParseIntError;

#[pyfunction]
fn parse_int(x: &str) -> Result<usize, ParseIntError> {
    x.parse()
}
```
```bash:REPL
>>> parse_int("bar")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: invalid digit found in string
```
この例ではRustの`std::num::ParseIntError`をPythonの`ValueError`に自動で変換してくれている。
[自前のエラーやサードパーティー製エラーも変換する手段はある](https://pyo3.rs/v0.17.3/function/error_handling.html#custom-rust-error-types)ので、気になった人は見てみるといいと思う。

# 4. [Python Classes](https://pyo3.rs/v0.17.3/class.html)
本命　分量がすごいので、今まで以上に自分に必要そうなところだけ端折って書いていきます
必要になったら[原著](https://pyo3.rs/v0.17.3/class.html)にあたってください(n回目)

めちゃくちゃざっくりまとめると、
1. エクスポートしたい構造体`struct`やC-likeな列挙体`enum`に`#[pyclass]`属性を与える
1. 構造体や列挙体に`impl`で実装されるメソッドに対して`#[pymethods]`属性を与える
    - `impl`の中にある関数のうち、特に`#[new]`をコンストラクタとしたい関数につける これがないとPython側からインスタンスを作れない
1. `#[pymodule]`属性がついたモジュールを初期化する関数の中でRust側の`#[pyclass]`属性がついた構造体や列挙体をPythonにバインドする

という流れらしい。実際に書くとこんな感じ：
```rust:lib.rs
use pyo3::prelude::*;
#[pyclass]
struct MyClass {
    num: i32,
}

#[pymethods]
impl MyClass {
    #[new]
    #[args(num = "-1")]
    fn new(num: i32) -> Self {
        MyClass { num }
    }

    fn getnum(&mut self) -> i32 {
        return self.num
    }

    #[args(num = "10")]
    fn setnum(&mut self, num: i32) {
        self.num = num
    }
}

#[pymodule]
fn my_module(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
    m.add_class::<MyClass>()?;
    Ok(())
}
```

```:REPL
>>> import my_module as m
>>> i = m.MyClass()
>>> i.getnum()
-1
>>> i.setnum(2)
>>> i.getnum()
2
```

こうしてみると簡単に見えるが、PythonとRustの言語的特色の違いからくる制約が色々とクラスに課されている。一つずつみていく

#### 1. lifetime parameterが使えない
Rustはコンパイル時にメモリ安全性を確かめる言語である以上、動的にデータを作ったり消したりするPythonでいつデータが落とされるかがわからない。つまり、一度Pythonにデータが渡るともはやRustのコンパイラにはライフタイムの追跡が不可能になるから、lifetime parameterを使うことができない。そのため、借用するデータは最低でも`'static`なライフタイムの必要がある。

#### 2. ジェネリクスが使えない
これめちゃくちゃ強烈。例えば`struct Foo<T>`のようなことができない。きつすぎんか…
どうもRustがコンパイル時に必要な`T`の型をすべて洗い出してそれらすべてに実装を生成するけど（知らなかった）、Python側でどんな使われ方をしているかわからないかららしい
pybind11だとテンプレート引数が使えたような気がしたけど、、、やっぱりコンパイル時にできる限り何でもしようとするRustでは難しいのかもしれない。

#### 3. [Send](https://doc.rust-jp.rs/book-ja/ch16-04-extensible-concurrency-sync-and-send.html)でなければならない
Pythonインタープリターが異なるスレッド間でデータを自由に使える必要があるから。まあ[だいたいの型はSendで、Sendな型からできるすべての型はSend](https://doc.rust-jp.rs/book-ja/ch16-04-extensible-concurrency-sync-and-send.html#:~:text=%E5%AE%8C%E5%85%A8%E3%81%AB,%E8%AD%B0%E8%AB%96%E3%81%97%E3%81%BE%E3%81%99%E3%80%82)なので問題はなさそう。Sendじゃない型を使いたいときは[`#[pyclass(unsendable)]`を使う](https://pyo3.rs/v0.17.3/class.html#customizing-the-class)

## PyCellと[内部可変性](https://doc.rust-jp.rs/book-ja/ch15-05-interior-mutability.html)
内部可変性って何？っていう人は[TRPLにかなりわかりやすく説明されている](https://doc.rust-jp.rs/book-ja/ch15-05-interior-mutability.html)ので一度見てみるといいと思う
端的に言えば、借用規則(1つの可変参照かたくさんの不変参照しか許さない)を、Rustは通常**コンパイル時**にチェックしてるけど、これを**実行時に**チェックしますよ～っていうのが内部可変性パターン

例えばPythonで作った`pyclass`のオブジェクトの中身をRust側でテストしたいなあとなったとき、内部可変性をもった`PyCell<T: PyClass>`を使うといいらしい。
詳しい使い方などは[原著(ry](https://pyo3.rs/v0.17.3/class.html#pycell-and-interior-mutability)

## クラスをカスタムする
`#[pyclass(parameter = ...)]`みたいな感じでクラスを色々カスタムできる
|パラメータ|ざっくりした説明|
|---|---|
|`crate = "some::path"`|`pyo3`クレートのパスを指定(普通はいらない)|
|`dict`|Pythonで`__dict__`を使うための下準備みたいなもの？|
|`extends = BaseType`|ベースクラス オブジェクトのタイプを決めるらしい|
|`freelist = N`|サイズ`N`の[フリーリスト](https://en.wikipedia.org/wiki/Free_list)を作る。頻繁に消したり作ったりする型に効果を発揮する|
|`mapping`|PyO3に`dict`のような[マッピング型](https://pyo3.rs/latest/class/protocols.html#mapping--sequence-types)であることを伝える|
|`module = "module_name"`|どのモジュール名のクラスとして表示するかPythonに伝える|
|`name = "class_name"`|Python側でどのような名前のクラスとして扱うか伝える|
|`sequence`|PyO3に`list`のような[シーケンス型](https://pyo3.rs/latest/class/protocols.html#mapping--sequence-types)であることを伝える|
|`subclass`|このクラスから継承できるようにする。列挙型は指定不可|
|`text_signature="(arg1, ...)"`|`__new__`のtext signatureを設定|
|`unsendable`|`Send`じゃない型を使いたいとき。なるべく避けるべき|
|`weakref`|このクラスへの[弱参照](https://ja.wikipedia.org/wiki/%E5%BC%B1%E3%81%84%E5%8F%82%E7%85%A7#:~:text=%E5%BC%B1%E3%81%84%E5%8F%82%E7%85%A7%EF%BC%88%E8%8B%B1%3A%20weak%20reference,%E8%A7%A3%E6%94%BE%E3%81%99%E3%82%8B%E3%81%93%E3%81%A8%E3%81%8C%E3%81%A7%E3%81%8D%E3%82%8B%E3%80%82)を許可する|

当面は`weakref`か`name`くらいしか使わなさそう。

## 継承
TBD
そもそも[Rustの設計思想では継承は推奨されていない](https://doc.rust-jp.rs/book-ja/ch17-01-what-is-oo.html#:~:text=%E7%B6%99%E6%89%BF%E3%81%AF%E3%80%81%E8%BF%91%E5%B9%B4%E3%80%81,%E5%88%B6%E9%99%90%E3%81%95%E3%82%8C%E3%81%BE%E3%81%99%E3%80%82)ので、個人的にはできれば避けたほうがいいと思う。

## getter, setter
`#[pyo3(get, set)]`を使うとgetterやsetterを自動で生成してくれる。
```rust
#[pyclass]
struct MyClass {
    #[pyo3(get, set)]
    num: i32
}
```
これでPython側で`instance.num = 3`のようにできる。これを使うには条件があって、
- `get`フィールドの型には`IntoPy<PyObject>`, `Clone`が実装されている
- `set`フィールドの型には`FromPyObject`が実装されている
もしこの条件をみたさなかったりgetterやsetterに副作用があるなら、自分で`#[getter]`, `#[setter]`属性のついた関数を定義する。

# Extra. Numpy使いたいが 
https://github.com/PyO3/rust-numpy

これを使おう！