Polars 2.0 RC переводит LazyFrame на потоковый движок и ломает тихие касты
Polars выпустила первый release candidate версии 2.0: все LazyFrame-запросы теперь идут через streaming-движок, а часть тихих приведений типов и заполнение null при склейке заменены ошибками. Что проверить в своих пайплайнах до финального релиза.

Команда Polars 2 сентября выпустила первый release candidate версии 2.0 библиотеки для работы с таблицами на Python и Rust. Финальный релиз обещан «в ближайшие недели». Главное изменение одно: любой вызов collect() у LazyFrame теперь по умолчанию выполняется потоковым движком, который обрабатывает промежуточные данные порциями и, по словам авторов, заметно снижает расход памяти. Автор библиотеки Ричи Винк пишет, что новых функций в 2.0 почти нет, а мажорная версия нужна, чтобы избавиться от старых архитектурных решений и поменять дефолты.
Для тех, кто гоняет Polars в пайплайнах, это значит два дела на сегодня. Во-первых, после обновления часть запросов может вернуть строки в другом порядке: потоковый движок не гарантирует порядок для join, group_by и unpivot, если явно не попросить. Во-вторых, код, который годами «работал» на неявных приведениях типов и молчаливом заполнении пропусков, начнёт падать с ошибкой. Разработчики называют это осознанной политикой: ошибка сразу лучше неверного результата через двадцать минут работы пайплайна.
Ключевые выводы
- RC ставится командой pip install polars==2.0rc1; финальный 2.0 выйдет в ближайшие недели, точной даты нет.
- LazyFrame.collect() по умолчанию идёт через streaming-движок; по ожиданиям авторов, в совокупности он «легко в 5 раз быстрее» старого in-memory и заметно экономит память; независимых замеров RC нет.
- Порядок строк после join, group_by и unpivot больше не гарантирован; для join и group_by порядок возвращает параметр maintain_order, для unpivot нужен явный sort; старый движок включается через pl.Config.set_engine_affinity("in-memory") или collect(engine="in-memory").
- is_in с разными типами, горизонтальный concat с разной высотой, касты строк в даты и целых в Enum теперь бросают исключение вместо тихого приведения.
- Большинство удалённых методов и параметров отвечают типизированными ошибками AttributeRemovedError и ArgumentRemovedError с подсказкой, чем заменить.
Почему потоковый движок потребовал мажорной версии
Polars давно развивает два движка. Классический in-memory собирает результат каждой операции целиком в памяти. Потоковый разбивает данные на куски и прогоняет их через план запроса конвейером, поэтому промежуточные результаты не раздуваются. До 2.0 потоковый движок нужно было включать явно; теперь режим engine="auto" выбирает именно его.
Цена такого дефолта в порядке строк: потоковый движок для ряда операций его не гарантирует. Старый движок порядок сохранял, и на это молча полагалось много кода: например, брали первую строку после группировки и считали её «самой ранней». В 2.0 такие места нужно найти и явно попросить порядок (у join и group_by есть параметр maintain_order, после unpivot остаётся явный sort):
Заявление о скорости стоит читать как оценку вендора: «в сумме мы ожидаем, что потоковый движок будет легко в 5 раз быстрее», со ссылкой на собственные бенчмарки Polars. Независимых замеров на RC пока нет, и выигрыш зависит от запроса: на маленьких таблицах, которые целиком помещаются в кеш, разница будет меньше, чем на группировках по десяткам гигабайт.
Где код перестанет молчать об ошибках
Вторая тема релиза сформулирована в посте так: ошибки должны подниматься заранее, а не через 20 минут работы пайплайна, и неявное поведение при несовпадении данных должно включаться явно, а не быть дефолтом. Авторы отдельно отмечают, что строгость стала ценнее с приходом ИИ-агентов: агент может вызвать collect_schema(), проверить типы без чтения данных и быстро получить обратную связь. Примеры из поста и руководства по миграции:
- is_in с разными типами. Раньше Int64 и Float64 приводились к общему супертипу, даже если это теряло точность. В примере из поста идентификатор 9007199254740993 при касте в float64 округлялся до 9007199254740992 (граница, до которой float64 представляет все целые точно), и проверка по списку «помеченных» аккаунтов давала ложное совпадение. В 2.0 это InvalidOperationError: кастовать нужно самому и осознанно.
- Горизонтальный concat. Таблицы высотой 5 и 4 раньше склеивались, а недостающая ячейка молча становилась null; типичный сценарий из поста: тихо упавшая задача за один из дней. Теперь ShapeError, а старое поведение включается через how="horizontal_extend".
- Касты заменены специализированными методами. Целые в Enum или Categorical и обратно: вместо cast() нужны .cat.to() и .cat.physical(). Строка в дату: вместо cast(pl.Date) методы .str.to_date() и .str.to_datetime(), которым можно явно задать формат. Кастовать плоскую колонку в List через cast(pl.List(...)) тоже нельзя, для этого есть pl.list().
- Булевы операторы между Boolean и целыми числами теперь ошибка; std() и var() для Duration удалены, сначала переводите в микросекунды через .dt.total_microseconds().
- Каст между Struct с разным числом полей при strict=True (дефолт) падает, а не обрезает лишние поля; руководство помечает это отдельным предупреждением, потому что раньше данные терялись молча.
Отдельный набор изменений касается чтения CSV. При сканировании набора файлов схема теперь выводится по первым 10 файлам, а не по всем (параметр infer_schema_files). Автоматические имена колонок для файлов без заголовка начинаются с column_0, а не column_1; это же касается read_excel и read_ods. Пользовательская схема в scan_csv сопоставляется с файлом по именам колонок, а не по позиции: раньше первая запись схемы молча получала данные первой колонки файла, даже если имена не совпадали. Для лишних и недостающих колонок появились параметры extra_columns и missing_columns, по умолчанию оба бросают ошибку.
Что будет со старым кодом
Для большинства удалений библиотека получила два типизированных исключения (документация предупреждает, что часть удалённого по-прежнему даёт обычные AttributeError и TypeError): polars.exceptions.AttributeRemovedError для удалённых методов и атрибутов и polars.exceptions.ArgumentRemovedError для удалённых параметров. Оба сообщения указывают на замену:
По словам Винка, большая часть удалённого давно помечена как deprecated, и у тех, кто обновлялся регулярно, пайплайны пострадать не должны. Команда просит сообщать, если из библиотеки убрали что-то, на что реально полагались. Смена движка не затрагивает eager-API DataFrame: выигрыша в скорости там не будет, потому что дефолт меняется только у LazyFrame. Остальные несовместимости на eager распространяются полностью: строгий concat, новые правила read_csv, убранные касты и удалённые методы.
Как проверить свой проект до финального релиза
- Поставьте RC в отдельное окружение: pip install polars==2.0rc1. В прод его тащить рано, это кандидат в релиз.
- Прогоните тесты и посмотрите на исключения AttributeRemovedError, ArgumentRemovedError, InvalidOperationError и ShapeError: каждое сообщение содержит подсказку с заменой.
- Найдите места, где код полагается на порядок строк после join, group_by или unpivot: сортировки «по умолчанию», head(1) после группировки, сравнение с эталоном по позициям. Добавьте maintain_order (join, group_by) или явный sort (unpivot).
- Если результат обязан совпадать со старым до байта, зафиксируйте старый движок на время миграции: pl.Config.set_engine_affinity("in-memory").
- Проверьте чтение CSV без заголовка (сдвиг нумерации колонок на единицу) и scan_csv с явной схемой (сопоставление по именам).
В планах ветки 2.x, о которых команда, по её словам, «недостаточно говорила публично»: полноценная out-of-core обработка для потокового движка, новая архитектура IO-плагинов, собственный читатель S3, расширенное покрытие SQL, планировщик на основе оценки стоимости с переупорядочиванием join и отказ от mmap, после которого конвейер станет асинхронным от начала до конца. Сроков по этим пунктам нет. Замечания по RC команда принимает в issues на GitHub.
Источники: Pre-release of Polars 2.0 (блог Polars, Ritchie Vink), Руководство по переходу на Polars 2.0, Бенчмарки Polars
Изображение на обложке: Логотип: Polars












