我想通过 Polars 表达式实现以下目标,而不是逐行映射元素,但我一直无法找到方法。
import polars
def foo():
return 1 + 1
def bar():
return 1 + 1
def baz():
return 2 + 2
exprs = 1_000_000 * [
"foo() == bar()",
"bar() == baz()",
"foo() == baz()",
"foo() > bar()",
"bar() < baz()",
"foo() != baz()",
]
df = polars.DataFrame({"exprs": exprs})
df.with_columns(
polars.col("exprs").map_elements(
lambda x: eval(x), return_dtype=polars.datatypes.Boolean
)
)
shape: (6_000_000, 1)
┌───────┐
│ exprs │
│ --- │
│ bool │
╞═══════╡
│ true │
│ false │
│ false │
│ false │
│ true │
│ … │
│ false │
│ false │
│ false │
│ true │
│ true │
└───────┘
问题在于
foo()
和
bar()
是这并不简单,因此
map_elements()
需要一些时间才能完成,因为
DataFrame
很大。
不幸的是,你无法使用 Polars 表达式直接实现
eval()
的功能。Polars 表达式旨在实现高效、向量化的操作,而
eval()
本质上依赖于 Python 解释器,这会导致性能瓶颈,尤其是在大型数据集上。
你正在使用的
map_elements()
方法实际上已经是处理此类情况的比较有效的方法了。但是,有一些方法可以优化代码以提高性能:
-
使用
apply
而不是map_elements
: 对于非向量化操作,apply
方法通常比map_elements
更快。
df.with_columns(
polars.col("exprs").apply(lambda x: eval(x)).cast(polars.Boolean)
)
-
预编译表达式:
如果你反复使用相同的表达式,可以考虑使用
compile()
函数预编译它们,然后在eval()
中使用编译后的代码对象。
compiled_exprs = [compile(expr, '<string>', 'eval') for expr in exprs]
df = polars.DataFrame({"exprs": compiled_exprs})
df.with_columns(
polars.col("exprs").apply(lambda x: eval(x)).cast(polars.Boolean)
)
-
使用其他库进行动态代码执行:
如果你需要更高级的动态代码执行功能,可以考虑使用
eval()
的替代方案,例如ast.literal_eval()
(用于安全评估表达式)或exec()
(用于执行更复杂的代码块)。但是,请注意使用这些函数时要格外小心,因为它们可能会带来安全风险,尤其是在处理来自不受信任来源的输入时。
请记住,在 Polars 中执行动态代码评估总是会导致性能下降。如果可能,请尝试重新设计你的代码以避免使用
eval()
,并使用 Polars 表达式来实现最佳性能。