Python тормозит? Время позвать тяжёлую артиллерию
Ты пишешь на Python. Данных много. Цикл for обрабатывает миллионы строк. И каждый раз, когда смотришь на прогресс-бар, думаешь: "Ну вот, опять можно сходить за кофе". Знакомо? Интерпретатор Python - не гоночный болид. Для CPU-bound задач он просто не создан.
Вариантов ускорения несколько: Cython, Numba, переписать на C++. Но есть один способ, который сочетает скорость нативного кода, безопасность памяти и современный инструментарий. Rust. Да, тот самый язык, который все хвалят за производительность и отсутствие segfault'ов.
Зачем тебе это? Если в проекте есть "узкое горлышко" - алгоритм, валидация данных, математические вычисления - его можно вынести в Rust и получить прирост в 10-100 раз. Как в Pydantic v2, где валидация работает на Rust под капотом.
Важно: Rust не заменит весь Python. Это дополнение для критических участков. Весь интерфейс, бизнес-логика высокого уровня остаются на Python.
Что происходит под капотом? Без магии
Python умеет вызывать функции из нативных библиотек через C ABI. Rust компилируется в ту же нативную библиотеку (.so на Linux, .dylib на macOS, .dll на Windows). Специальные крейты (пакеты Rust) типа PyO3 создают "мостик", который преобразует Python-объекты в Rust-типы и обратно.
Это не новая идея. Но PyO3 в 2026 году достиг невероятной зрелости. Версия 0.22 (актуальна на апрель 2026) поддерживает все последние фичи Python 3.12 и 3.13, асинхронные функции, GIL management и даже возможность писать классы на Rust, которые выглядят как родные Python-классы.
Собираем инструменты: что нужно установить
Перед началом убедись, что система готова. Тебе понадобится:
- Rust 1.85 или новее (на апрель 2026 стабильная версия 1.87). Устанавливается через rustup
- Python 3.10+ (рекомендую 3.12 или 3.13)
- Maturin 1.5+ - инструмент для сборки и публикации Rust-библиотек для Python
- PyO3 0.22+ - основной крейт для связки Rust-Python
Установка займёт 5 минут:
# Установка Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Установка Maturin
pip install maturin
# или через pipx, что лучше для изоляции
pipx install maturin
1Создаём новый проект
Maturin умеет создавать проекты с готовой структурой. Выполни в терминале:
maturin init --bindings pyo3 my_fast_module
cd my_fast_module
Посмотри, что создалось: структура Rust-библиотеки с Cargo.toml и файлом lib.rs. Внутри уже есть пример функции sum_as_string.
Не называй проект просто "rust" или "fast". Имя станет именем Python-модуля. Лучше что-то вроде "data_processor_rs" или "fast_algorithms".
2Пишем первую полезную функцию
Открой src/lib.rs. Замени содержимое на реальный пример. Допустим, нам нужно быстро фильтровать список чисел, оставляя только чётные:
use pyo3::prelude::*;
/// Фильтрация чётных чисел из списка
/// Принимает список Python, возвращает новый список
#[pyfunction]
fn filter_even_numbers(py: Python, numbers: Vec) -> PyResult> {
// В Rust это просто: filter и collect
let result: Vec = numbers
.into_iter()
.filter(|&x| x % 2 == 0)
.collect();
Ok(result)
}
/// Модуль на Rust, который станет Python-модулем
#[pymodule]
fn my_fast_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(filter_even_numbers, m)?)?;
Ok(())
}
Что здесь важно? Аннотация #[pyfunction] говорит PyO3, что эту функцию нужно экспортировать в Python. PyResult - специальный тип для возврата результатов или Python-исключений.
3Компилируем и устанавливаем
Теперь самое интересное - превращаем Rust-код в Python-модуль. В директории проекта выполни:
# Разработческий режим: устанавливает модуль в текущее виртуальное окружение
maturin develop
# Или для релизной сборки (оптимизированной)
maturin develop --release
Maturin сам найдёт активное виртуальное окружение Python, скомпилирует Rust-код и установит модуль. Если виртуального окружения нет, создай его заранее.
4Используем в Python
Открой Python-интерпретатор или создай test.py:
import my_fast_module
# Генерируем большой список
numbers = list(range(1_000_000))
# Вызываем Rust-функцию
result = my_fast_module.filter_even_numbers(numbers)
print(f"Найдено {len(result)} чётных чисел")
# Вывод: Найдено 500000 чётных чисел
Вот и всё. Функция работает. Но насколько быстрее? Давай измерим.
Бенчмарки: холодные цифры вместо громких слов
Создадим более сложный пример - вычисление суммы квадратов чисел. Сравним чистый Python, NumPy и Rust.
Добавим в lib.rs новую функцию:
#[pyfunction]
fn sum_of_squares(numbers: Vec) -> PyResult {
let sum: f64 = numbers
.into_iter()
.map(|x| x * x)
.sum();
Ok(sum)
}
// Не забудь добавить её в модуль:
// m.add_function(wrap_pyfunction!(sum_of_squares, m)?)?;
Теперь Python-скрипт для сравнения:
import timeit
import numpy as np
import my_fast_module
# Готовим данные
data = list(range(1_000_000))
data_np = np.array(data, dtype=np.float64)
# Python версия
def sum_squares_python(nums):
return sum(x * x for x in nums)
# NumPy версия
def sum_squares_numpy(arr):
return np.sum(arr ** 2)
# Измеряем
python_time = timeit.timeit(
lambda: sum_squares_python(data),
number=10
)
numpy_time = timeit.timeit(
lambda: sum_squares_numpy(data_np),
number=10
)
rust_time = timeit.timeit(
lambda: my_fast_module.sum_of_squares(data),
number=10
)
print(f"Python: {python_time:.3f} сек")
print(f"NumPy: {numpy_time:.3f} сек")
print(f"Rust: {rust_time:.3f} сек")
| Метод | Время (10 запусков) | Ускорение относительно Python |
|---|---|---|
| Чистый Python | ~2.8 сек | 1x (база) |
| NumPy | ~0.15 сек | ~18x |
| Rust (PyO3) | ~0.08 сек | ~35x |
Rust в два раза быстрее NumPy для этой задачи. Почему? NumPy тратит время на создание промежуточного массива (arr ** 2). Rust работает с потоком данных без лишних аллокаций.
Ошибки, которые сломают тебе день
Интеграция языков - не игрушки. Вот что может пойти не так:
- Гонка за GIL: Rust-функция по умолчанию захватывает Global Interpreter Lock. Если функция выполняется долго, она блокирует весь Python-интерпретатор. Решение: использовать #[pyfunction(signature = (...))] с опцией gil = false или выносить вычисления в отдельные потоки.
- Утечки памяти: Rust автоматически управляет памятью, но если ты передаёшь Python-объекты в Rust и хранишь их дольше вызова функции, нужны специальные типы Py
и правильное отслеживание ссылок. - Паника в Rust: Если Rust-код вызовет panic!(), Python-процесс аварийно завершится. Всегда обрабатывай ошибки внутри Rust, возвращай PyResult.
Пример безопасной функции с обработкой ошибок:
#[pyfunction]
fn safe_divide(a: f64, b: f64) -> PyResult {
if b == 0.0 {
// Возвращаем Python-исключение, а не паникуем
Err(PyZeroDivisionError::new_err("division by zero"))
} else {
Ok(a / b)
}
}
Когда Rust действительно нужен, а когда нет
Не всё стоит переписывать. Вот эмпирическое правило:
- Да: Обработка больших массивов, математические вычисления, парсинг сложных форматов, криптография, симуляции.
- Нет: CRUD-операции, веб-роутинг, простые преобразования данных, скрипты автоматизации.
Если твой проект связан с AI-агентами, возможно, тебе интереснее посмотреть на оркестраторы на Rust, чем оптимизировать отдельные функции.
А что насчёт AOT-компиляции Python?
Есть альтернатива - компиляция Python в нативный код через Nuitka или Cython. Иногда этого достаточно. Подробнее в статье Python без тормозов. Но если нужен максимальный контроль над памятью и процессором, Rust вне конкуренции.
Что делать, если не хочешь учить Rust?
Честно? Учить всё равно придётся. Но можно начать с малого:
- Используй AI-инструменты типа Claude Code 2.0 для генерации Rust-кода по описанию.
- Бери готовые Rust-библиотеки с Python-биндингами (например, полярс для работы с данными).
- Начни с переписывания самых простых функций, которые уже есть в Python.
Или рассмотри вариант автоматического перевода уязвимого C-кода в Rust, как в великом рефакторинге.
Вопросы, которые ты хотел задать, но стеснялся
Сложно ли поддерживать такой гибридный проект?
Да, сложнее, чем чистый Python. Нужно следить за совместимостью версий PyO3 и Python, обновлять Rust-компилятор. Но Maturin автоматизирует большую часть работы. И главное - выноси в Rust только стабильные, редко меняющиеся компоненты.
Можно ли отлаживать Rust-код, вызванный из Python?
Можно, но это требует танцев с бубном. Проще всего - логировать из Rust в файл или использовать println!() для дебага. Для серьёзной отладки придёшься запускать Python-процесс под gdb/lldb и подключаться к Rust-символам.
Какие типы данных эффективнее всего передавать?
Примитивы (числа) и простые векторы/массивы. Сложные структуры вроде словарей требуют конвертации и замедляют всё. Дизайни API так, чтобы минимизировать преобразования типов на границе Rust-Python.
И последний совет: перед тем, как погружаться в Rust, измерь свой код. Найдите реальное "узкое место" через профайлер. Может оказаться, что проблема не в скорости вычислений, а в медленном I/O или неоптимальном алгоритме. Rust ускорит только CPU-bound операции, а ожидание базы данных ему не подвластно.