2026, Jan 12 15:01
pl.element и pl.all в Polars list.eval: есть ли разница
Чем отличаются pl.element и pl.all в Polars list.eval: разбор области видимости, причины одинаковых результатов и случаи, когда разница действительно важна.
При работе со списками в Polars часто используется list.eval, и здесь легко запутаться из‑за тонкости в названиях. В руководстве сказано, что к отдельному элементу можно обратиться через pl.element, а ко всем элементам — через pl.all. На практике внутри list.eval оба варианта выглядят взаимозаменяемыми, что закономерно вызывает вопрос: в чем реальная разница и когда это имеет значение?
Что говорится в документации
Функция eval дает доступ к элементам списка: pl.element ссылается на каждый отдельный элемент, но можно также использовать pl.all(), чтобы ссылаться на все элементы списка.
Демонстрация проблемы
Рассмотрим столбец-список и один и тот же набор выражений, вычисленных сначала с pl.element, а затем с pl.all. В обоих случаях результат совпадает.
import polars as pl
frame = pl.DataFrame({
"arr": [[1], [3, 2], [6, 4, 5]]
})
print(frame)
out_elem = frame.with_columns(
pl.col("arr").list.eval(pl.element()**2).alias("square"),
pl.col("arr").list.eval(pl.element().rank()).alias("rank"),
pl.col("arr").list.eval(pl.element().count()).alias("count")
)
out_all = frame.with_columns(
pl.col("arr").list.eval(pl.all()**2).alias("square"),
pl.col("arr").list.eval(pl.all().rank()).alias("rank"),
pl.col("arr").list.eval(pl.all().count()).alias("count")
)
print(out_elem.equals(out_all))
Проверка на равенство возвращает True, что наводит на мысль, что в этом контексте практической разницы нет.
Что происходит на самом деле
pl.all() без аргументов ссылается на все столбцы, доступные в текущем контексте. Внутри list.eval доступен только сам список как единый «столбец» элементов. Поскольку в области видимости нет других столбцов, pl.all() внутри list.eval фактически указывает на те же данные, что и pl.element. Иными словами, для pl.all() в рамках list.eval нет особого смысла: оно ведет себя так же просто потому, что контекст сужается до элементов списка.
Этим же объясняется, почему два других селектора дают тот же результат внутри list.eval. Можно обратиться к списку по его внутреннему имени — это пустая строка — через pl.col(''), а можно выбрать «всё» через pl.col('*'). Оба варианта сводятся к одним и тем же данным в list.eval, потому что в этом микроконтексте доступен ровно один столбец для выбора.
Решение и краткая проверка
Ниже приведённый фрагмент демонстрирует эквивалентность pl.element, pl.all, pl.col('') и pl.col('*') в этой ситуации.
import polars as pl
from polars.testing import assert_frame_equal
tbl = pl.DataFrame({"seq": [[1, 2, 3]]})
def build_expr(expr):
return pl.col("seq").list.eval(expr.mul(2).add(1))
baseline = tbl.select(build_expr(pl.element()))
for expr in [pl.all(), pl.col(""), pl.col("*")]:
assert_frame_equal(
baseline,
tbl.select(build_expr(expr))
)
Почему это важно знать
Когда выражения выглядят по‑разному, но приводят к одному результату, легко начать искать несуществующие нюансы. Понимание того, что pl.all() ссылается на все столбцы в области видимости, а list.eval сужает эту область до элементов списка, снимает двусмысленность. Заодно становится ясно, почему альтернативные селекторы вроде pl.col('') и pl.col('*') ведут себя идентично именно в этом случае.
Выводы
Внутри list.eval pl.element и pl.all указывают на одни и те же данные, потому что контекст вычисления предоставляет только элементы списка. Если там нужен селектор, можно использовать любой из pl.element, pl.all, pl.col('') или pl.col('*') и получить одинаковое поведение. Это знание помогает писать более ясные выражения и не тратить время на поиск несуществующих различий.