DevGang
Авторизоваться

Улучшение Python с помощью Rust

Rust, иногда называемый Rustlang, является любимым и любимым языком многих программистов. Несмотря на то, что его создание в Mozilla произошло всего немногим более десяти лет назад!

Некоторые из вас, читающие это, возможно, случайно наткнулись на статью или видео на YouTube, рекламирующие этот новый и невероятно эффективный язык, но что это такое?

Rust — это язык программирования со статической типизацией, разработанный для повышения производительности и безопасности, особенно для безопасного параллелизма и управления памятью. Его синтаксис похож на синтаксис C++. Поскольку в Rust нет постоянно работающего сборщика мусора, его проекты могут использоваться в качестве библиотек другими языками программирования через интерфейсы сторонних функций. Это идеальный сценарий для существующих проектов, где важно обеспечить высокую производительность при сохранении безопасности памяти.

Что еще более впечатляет, согласно опросу Stack Overflow, Rust оставался «самым любимым» языком сообщества в течение шести лет!

Постоянный фаворит

Python, с другой стороны, сумел стабильно оставаться в тройке самых популярных языков в течение последних нескольких лет. Хотя есть те, кто придерживается мнения, что Python немного переоценен и, возможно, не заслуживает того, чтобы быть в верхних рейтингах.

Диаграмма ниже из другого опроса Stack Overflow — лишь одна из нескольких, которые показывают аналогичную тенденцию, обрисовывающую в общих чертах постоянную популярность Python:

Затянувшаяся проблема

Итак, мы установили, что Python — чрезвычайно популярный и (все еще) развивающийся язык, но неужели это все? Нет, даже несмотря на то, что последний выпуск Python 3.11 значительно повысил скорость языка (и они существенны!), в комнате все еще есть огромный слон, на которого укажут многие опытные программисты, объясняя, почему они этого не делают. Не пишите все только на Python: это интерпретируемый язык, которому, как правило, не хватает производительности в больших объемах и на низком уровне.

Однако интерпретируемый язык никоим образом не делает его плохим! Есть причина (ну, несколько), по которой Python продолжает сохранять свое господство в качестве действительно любимого и простого в использовании языка.

Однако проблема заключается в том, что, поскольку это интерпретируемый язык, его необходимо запускать через компилятор just in time, который находится в целевой системе во время выполнения. Это делает его немного менее эффективным, чем другие языки, такие как Rust, Go и C/C++, которые все компилируются в собственные двоичные файлы машинного кода, которые системы могут запускать немедленно без необходимости в промежуточной программе.

Практичное решение

С уверенностью можно сказать, что многие из вас хотели использовать отличную читабельность, гибкость и объем пакета, которые предлагает Python. Но также добиться более высокого повышения производительности, которое предлагает Rust, когда необходимо выполнять интенсивные низкоуровневые задачи.

Как бы то ни было, есть отличный вариант, который позволяет нам достичь обеих этих целей.

В оставшейся части этой статьи мы рассмотрим отличный пакет под названием pyo3, который позволит нам писать и публиковать наши собственные пакеты Python с использованием Rust. После этого мы можем установить и импортировать их в наш проект Python, используя pip, точно так же, как и для любого другого модуля.

Реальная потребность в подобном проекте может исходить из ряда различных сценариев:

  • У вас есть команда, которая более опытна в создании приложений командной строки на основе Python, но вам нужно включить некоторые интенсивные I/O задачи, для достижения которых требуется более эффективный низкоуровневый язык, такой как Rust.
  • Вы уже разработали проект с использованием Python, но хотите, чтобы будущие дополнения к коду были написаны на Rust, чтобы воспользоваться преимуществами его производительности и безопасности ресурсов.
  • Простой синтаксис Python и множество сторонних пакетов делают его отличным выбором для создания терминального приложении. Объедините это с некоторым родным кодом Rust для низкоуровневых задач, и вы получите чертовски фреймворк в вашем распоряжении!

Независимо от того, по какой причине вам может понадобиться такая гибридная структура проекта, цель остается неизменной:

Объедините лучшее из двух языков для создания чрезвычайно производительных, безопасных для памяти и фантастически выглядящих программ.

После того, как вы соедините части этого небольшого проекта, у вас должно быть хорошее представление о доступных вам возможностях, если вы когда-нибудь захотите или вам понадобится использовать Rust для улучшения приложения на основе Python!

Начало

Весь проект, который мы здесь создадим, будет состоять из двух подразделов:

  1. Rust Crate, состоящий из тестовых функций, которые наше приложение Python будет импортировать и вызывать (часть 1)
  2. Приложение Python, которое будет служить основной базой для нашего интерфейса командной строки (часть 2)

На первый взгляд может показаться, что работы много, но на самом деле все относительно просто и легко разрабатывать и расширять.

Если вы хотите загрузить пример кода для этой части проекта, не стесняйтесь посетить и клонировать его репозиторий на GitHub: https://github.com/dedSyn4ps3/rusty-python

Создание Rust Crate

Прежде всего, если у вас не установлен Rust и его менеджер пакетов Cargo, вы можете сделать это очень быстро.

  • В Unix запустите curl https://sh.rustup.rs -sSf | sh в вашей оболочке. Это загружает и запускает rustup-init.sh, который, в свою очередь, загружает и запускает правильную версию исполняемого файла rustup-init для вашей платформы.
  • В Windows загрузите и запустите rustup-init.exe.

Чтобы создать основу для нашего нового ящика, мы собираемся использовать два важных инструмента, pyo3 и maturin. Последний из которых позволит нам легко создать скелет, необходимый для экспорта нашего кода Rust для использования с Python. Установить эти последние требования очень просто, и в зависимости от вашей операционной системы это может быть достигнуто следующим образом:

  • Пользователи macOS могут установить с помощью Homebrew, запустив brew install maturin.
  • Все могут установить его глобально, используя pip install maturin.
  • Рекомендуемый подход — установить maturin в изолированной виртуальной среде с помощью pipx.

После этого перейдите к месту, где вы хотите создать проект, а затем, используя предпочитаемую оболочку, выполните следующее:

maturin new -b pyo3 rusty-python && cd rusty-python

Это сгенерирует нашу базовую структуру проекта, включая необходимый файл Cargo.toml, в который мы добавим необходимые зависимости, а также файл pyproject.toml, который Python будет использовать для сборки и установки нашего пакета, когда придет время. Флаг -b pyo3 сообщает maturin, что мы будем использовать эту структуру для создания нашего нового модуля.

Наш новый проект
Наш новый проект

Строительные блоки

Вы заметите, что maturin также создал для нас файл src/lib.rs. Здесь будет находиться весь код и функциональность нашего ящика.

Он уже содержит некоторый простой шаблон, чтобы показать, как мы можем создать экспорт функций для нашего приложения Python:

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.
#[pymodule]
fn rusty_python(_py: Python, m: &PyModule) -> PyResult<()> {
 m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
 Ok(())
}

Наш crate состоит из различных определений функций, которые снабжены атрибутом pyfunction. Чтобы использовать эти функции, мы определяем другое тело функции, аннотированное атрибутом pymodule, а затем реализуем pyfunction(s) с помощью add_function :

/// Says hello.
#[pyfunction]
fn say_hello(name: &str) {
 println!("Hello there {name}!");
}

/// Runs several test loops
#[pyfunction]
fn run_loops() {
 logger::info("Running test loops...");
 let mut _count: u32 = 0;  
 for _ in 0..1000 {
  for _ in 0..100 {
   _count += 1;
  }
 } 
 print!("\n");
 logger::debug("Process Finished");
}

#[pymodule]
fn rusty_python(_py: Python, m: &PyModule) -> PyResult<()> {
 m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
 m.add_function(wrap_pyfunction!(say_hello, m)?)?;
 m.add_function(wrap_pyfunction!(run_loops, m)?)?;
 Ok(())
}

Сам по себе шаблон предоставляет нам рабочее решение для тестирования Python, но в демонстрационных целях мы продолжим и создадим дополнительную pyfunction, которая печатает сообщение Hello, используя предоставленные входные данные. Вы можете увидеть в информации о коде выше, как это сделать:

  • Объявите две новые функции, используя атрибут pyfunction
  • Добавьте только что созданные pyfunctions в наш модуль rusty-python.

Загрузка Crate

Чтобы легко установить наш новый Rust crate и импортировать его в будущие проекты Python, мы обязательно загрузим его в систему контроля версий, такую как Github или Gitlab (или любую другую службу, которую вы можете использовать).

Для тех, кто не знаком с процессом создания репозитория и отправки в него нашего кода, вот краткое изложение:

  • Войдите в свою учетную запись GitHub/Gitlab и создайте новый пустой репозиторий.
  • Вернитесь к своему терминалу и из корня каталога вашего проекта запустите git init.
  • После того, как вы инициализировали что-то, вам нужно связать его с вашим удаленным репозиторием, чтобы вы могли отправлять в него все новые изменения. Просто запустите следующее:
git remote add origin https://github.com/<username>/<repo-name>.git
git branch -M main
git add .
git commit -m "First Commit"
git push -u origin main

Вкратце, вот что нам нужно было собрать для нашего гибридного приложения Rust-Python:

Если вы хотите продолжить работу с кодом для этого раздела проекта, перейдите в репозиторий GitHub и клонируйте его: https://github.com/dedSyn4ps3/python-with-rust.

Создание приложения Python

Теперь пришло время закончить последний этап нашего проекта. Если вы еще не заметили, нам нужно установить Python, чтобы закончить;

  • Для пользователей Linux просто откройте терминал и запустите sudo pacman install python3, sudo apt install -y python3 и т. д. в зависимости от вашего менеджера пакетов. В вашей системе уже должен быть установлен Python, но я определенно рекомендую иметь по крайней мере v3.8+ (еще лучше было бы установить 3.11, которая немного быстрее).
  • В Windows и macOS вы можете загрузить исполняемые файлы установщика с сайта python.org.

Чтобы собрать этот небольшой пример, мы воспользуемся несколькими пакетами, чтобы обеспечить рабочую базу кода, которую можно расширять и использовать в других проектах.

Строительные блоки

Создайте новую папку проекта для приложения Python и создайте пару файлов примеров для тестирования:

├── api
│   ├── __init__.py
│   └── update.py
└── cmd.py

Каталог модуля API просто содержит пару функций, которые мы можем использовать в нашем основном приложении командной строки для моделирования типичного импорта приложений:

import time
from rich.progress import track

def checkVersion(name):
 version = "1.0.0"
 getUpdate(f"Fetching current version for {name}...")
 print(f"\n[+] All Good! You're using the most recent of {name} --> {version}\n")

def getUpdate(description):
 for i in track(range(100), description=description):
  time.sleep(.1) # Simulate work being done

Если вы заметили, что оператор импорта ссылается на rich и не знаком с библиотекой, проверьте это. Одна из главных причин, по которой всегда нравилось собирать терминальные приложения Python, связана с огромной гибкостью и возможностями библиотеки, которые он предлагает разработчикам, стремящимся создать что-то великолепное.

В основном файле cmd.py находится логика нашего приложения командной строки:

#!/usr/bin/env python
import argparse
from api import update
import rusty_python as rp

d = """
API test application using a combination
of pure Python functions and additional
helper modules written in Rust.
This application uses several Python libraries to
create a colorful commandline app with example
functionality implemented in Rust
"""

def cli():
 parser = argparse.ArgumentParser(
  description=d
 )

 parser.add_argument(
  "-n", "--app-name",
  action="store",
  required=False,
  help="App name to use for update download simulation"
 )

 parser.add_argument(
  "-l", "--loop",
  action="store_true",
  required=False,
  help="Run some test loops!. Uses `run_loops` implemented in Rust"
  )

 parser.add_argument(
  "-r", "--rust-arg",
  action="store",
  required=False,
  help="""
  Tell us your name! This `string` value gets passed
  to the `say_hello` function implemented in Rust. The function also runs
  multiple `async` requests
  """
 )
 
 args = parser.parse_args()

 if args:
  if args.app_name:
   update.checkVersion(args.app_name)
  if args.loop:
   rp.run_loops()
  if args.rust_arg:
   rp.say_hello(args.rust_arg)
   rp.begin_request_test()
 else:
  parser.print_usage()  

if __name__ == '__main__':
 cli()

Быстрый анализ

Несмотря на то, что приведенный выше код довольно прост, это именно тот тип структуры, который вы будете использовать в будущих проектах по мере их усложнения и масштабирования. Дизайн следует тем же стандартным рекомендациям при сборке приложения CLI:

  • Установите и импортируйте библиотеку argparse для обработки определения и чтения входных аргументов во время выполнения программы.
  • Определите нашу основную функцию cli для обработки всей логики программы.
  • Создайте и создайте экземпляр нового объекта parser, а также определите различные флаги и аргументы, которые будет принимать приложение.

Собираем все вместе

Наконец-то пришло время протестировать проект и использовать наш пользовательский пакет, реализованный на Rust. Однако прежде чем прыгнуть вперед и запустить cmd.py, имейте в виду, что пара модулей, импортированных в наше приложение, не сразу доступна для использования Python.

Помните, мы включили rich библиотеку, чтобы помочь улучшить внешний вид нашего приложения и предоставить дополнительные функции по мере роста нашего проекта. Мы также добавили оператор импорта, чтобы позволить нам вызывать функции из нашего пакета, реализованного в Rust.

Чтобы поставить последние части на место, нам просто нужно запустить пару команд pip для установки недостающих зависимостей:

$ pip install rich
$ pip install -i https://test.pypi.org/simple/ rusty-python

После установки всех последних зависимостей мы наконец-то можем запустить наше новое маленькое приложение командной строки и протестировать. Как и в случае с большинством приложений CLI, обычно рекомендуется запускать код без каких-либо аргументов, кроме -h или — help, чтобы убедиться, что наша информация об использовании распечатывается для наших пользователей:

После этого мы можем протестировать обычные функции Python, передав аргументы приложению. А затем сделать то же самое для функции из нашего пакета Rust. Если все пойдет хорошо, не должно быть никаких непредвиденных ошибок или сбоев, вывод должен быть таким же, как и любой другой нормальный вызов функции.

Завершение

Это может показаться немного разочаровывающим после прохождения различных шагов, чтобы добраться до этой точки, но на самом деле вам удалось включить функциональность, реализованную в Rust, и использовать ее в приложении Python. С этого момента ваши возможности практически безграничны.

#Python #Data Science
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

В этом месте могла бы быть ваша реклама

Разместить рекламу