Вызов Rust из Python с помощью pyo3

Вызов 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
    }
}

  1. Макрос pyfunction позволяет использовать функцию в Python
  2. Используйте обычные типы Rust для параметров; pyo3 может их конвертировать
  3. Нам нужно вернуть тип PyResult, который является псевдонимом Result<T, PyErr>
  4. Вернуть конкретную ошибку Python, если команда не соответствует
  5. 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(())
    }
    
    1. Добавьте функцию в модуль
    2. На этом этапе мы можем использовать 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()
      

      1. Обычный импорт Python
      2. Смотри, ма, это работает!
      3. Заключение

        В этом посте я улучшил низкоуровневую интеграцию с ctypes до общей готовой к использованию библиотеки pyo3. Однако я едва коснулся поверхности; pyo3 — мощная, хорошо поддерживаемая библиотека с множеством функций.

        Я хочу поблагодарить всех, кто указал мне в этом направлении.

        Полный исходный код этой публикации можно найти на GitHub.

        Дальше:

        [1] Руководство пользователя PyO3

        [2] зрелость


        :::информация Первоначально опубликовано на сайте A Java Geek 29 октября 2023 г.

        :::


        Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE