Вызов Rust из Python с помощью pyo3
2 ноября 2023 г.Я получил множество отзывов на свой пост о вызове Rust из Python:
* Новости хакеров * /r/python * /r/rust
Во многих комментариях упоминался pyo3
, и мне следует использовать его вместо того, чтобы готовить самостоятельно. Спасибо авторам, я проверил: в этом посте я объясняю, что это такое и как я перенес свой код.
Что такое pyo3?
<блок-цитата>Привязки Rust для Python, включая инструменты для создания собственных модулей расширения Python. Также поддерживается запуск и взаимодействие с кодом Python из двоичного файла Rust.
— Руководство пользователя PyO3 [1]
Действительно, pyo3
подходит для моего варианта использования, вызывая Rust из Python. Более того, он обрабатывает преобразование типов Python в типы Rust и обратно. Наконец, он предлагает утилиту maturin
, которая упрощает взаимодействие между проектом Python и проектом Rust.
Мэтурин
<блок-цитата>Создавайте и публикуйте крейты с привязками pyo3, Rust-cpython, cffi и uniffi, а также двоичными файлами Rust в виде пакетов Python.
— Мэтьюрин на GitHub [2]
maturin
доступен через pip install
. Он предлагает несколько команд:
* new
: создайте новый проект Cargo с настроенным Maturin.
* build
: создайте колеса и сохраните их локально.
* publish
: встройте крейт в пакет Python и опубликуйте его в pypi.
* develop
: создайте крейт как модуль Python непосредственно в текущей виртуальной среде, сделав его доступным для Python
Обратите внимание, что Maturin начинался как сопутствующий проект pyo3
, но теперь предлагает привязки Rust-cpython, cffi и uniffi.
Миграция проекта
Термин «миграция» здесь немного вводит в заблуждение, поскольку мы начнем с нуля, чтобы соответствовать использованию Мэтьюрина. Однако мы достигнем того же конечного состояния. Я не буду перефразировать руководство, поскольку оно работает без проблем. В конечном итоге у нас есть полнофункциональный проект Rust с единственной функцией sum_as_string()
, которую мы можем вызывать в оболочке Python. Обратите внимание на зависимость от pyo3
:
pyo3 = "0.20.0"
Второй шаг — повторное использование материала из предыдущего проекта. Сначала мы добавляем нашу функцию compute()
в конец файла lib.rs
:
#[pyfunction] //1
fn compute(command: &str, a: Complex<f64>, b: Complex<f64>) -> PyResult<Complex<f64>> { //2-3
match command {
"add" => Ok(a + b),
"sub" => Ok(a - b),
"mul" => Ok(a * b),
_ => Err(PyValueError::new_err("Unknown command")), //4
}
}
- Макрос
pyfunction
позволяет использовать функцию в Python - Используйте обычные типы Rust для параметров;
pyo3
может их конвертировать - Нам нужно вернуть тип
PyResult
, который является псевдонимомResult<T, PyErr>
- Вернуть конкретную ошибку Python, если команда не соответствует ол>
- Добавьте функцию в модуль ол>
- Обычный импорт Python
- Смотри, ма, это работает! ол>
pyo3
автоматически обрабатывает преобразование для большинства типов. Однако комплексные числа требуют дополнительной функции. Нам также необходимо перейти из крейта num
в num-complex
:
pyo3 = { version = "0.20.0" , features = ["num-complex"]}
num-complex = "0.4.4"
Чтобы преобразовать пользовательские типы, необходимо реализовать черты FromPyObject
для параметров и ToPyObject
для возвращаемых значений.
Наконец, нам нужно только добавить функцию в модуль:
#[pymodule]
fn rust_over_pyo3(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
m.add_function(wrap_pyfunction!(compute, m)?)?; //1
Ok(())
}
На этом этапе мы можем использовать Maturin для тестирования проекта:
maturin develop
После завершения компиляции мы можем запустить оболочку Python в виртуальной среде:
python
>>> from rust_over_pyo3 import compute
>>> compute('add',1+3j,-5j)
(1-2j)
>>> compute('sub',1+3j,-5j)
(1+8j)
Последние штрихи
Приведенная выше настройка позволяет нам использовать Rust из оболочки Python, но не из файла Python. Чтобы использовать значение по умолчанию, мы должны создать проект Python внутри проекта Rust, имя которого соответствует имени модуля Rust. Поскольку я назвал свою библиотеку rust_over_pyo3
, вот общая структура:
my-project
├── Cargo.toml
├── rust_over_pyo3
│ └── main.py
├── pyproject.toml
└── src
└── lib.rs
Чтобы использовать библиотеку Rust в Python, нам нужно сначала ее собрать.
maturin build --release
Мы вручную перемещаем артефакт из /target/release/maturin/librust_over_pyo3.dylib
в rust_over_pyo3.so
в пакете Python. Вместо этого мы также можем запустить cargo build --release
; в этом случае исходный файл находится непосредственно в /target/release
.
На этом этапе мы можем использовать библиотеку как любой другой модуль Python:
from typing import Optional
from click import command, option
from rust_over_pyo3 import compute (1)
@command()
@option('--add', 'command', flag_value='add')
@option('--sub', 'command', flag_value='sub')
@option('--mul', 'command', flag_value='mul')
@option('--arg1', help='First complex number in the form x+yj')
@option('--arg2', help='Second complex number in the form x'+y'j')
def cli(command: Optional[str], arg1: Optional[str], arg2: Optional[str]) -> None:
n1: complex = complex(arg1)
n2: complex = complex(arg2)
result: complex = compute(command, n1, n2) (2)
print(result)
if __name__ == '__main__':
cli()
Заключение
В этом посте я улучшил низкоуровневую интеграцию с ctypes
до общей готовой к использованию библиотеки pyo3
. Однако я едва коснулся поверхности; pyo3
— мощная, хорошо поддерживаемая библиотека с множеством функций.
Я хочу поблагодарить всех, кто указал мне в этом направлении.
Полный исходный код этой публикации можно найти на GitHub.
Дальше:
[1] Руководство пользователя PyO3
[2] зрелость
:::информация Первоначально опубликовано на сайте A Java Geek 29 октября 2023 г.
:::
Оригинал