Pandas Series.apply调用eval/pd.eval时提示nan未定义是何原因
根本原因
问题核心是命名空间取值的作用域规则和直觉预期不一致,两种eval报错的具体原因如下:
1. 原生内置eval报NameError的原因
Python内置eval是C实现的函数,当不显式传入globals/locals参数时,它会从当前调用栈中最近的Python帧里获取命名空间:
- 你在代码顶层(
__main__模块全局作用域)直接执行eval(lst)时,最近的Python帧就是__main__模块的全局帧,这个帧里有你定义/导入的nan,所以能正常运行。 - 当你把
eval直接作为参数传给Series.apply时,eval实际是在pandas内部Cython编译实现的循环逻辑pandas._libs.lib.map_infer中被调用的,这时候离eval最近的Python帧属于pandas内部模块,不是你写代码的__main__模块,该帧的命名空间里自然没有你定义的nan,因此抛出名称错误。
你在顶层打印的globals()/locals()是__main__帧的命名空间,和eval被调用时实际拿到的命名空间完全不是同一个对象,所以就算打印结果里能看到nan,eval也根本访问不到。
2. pd.eval报KeyError的原因
pd.eval默认确实会通过栈回溯,获取直接调用它的Python代码所在帧的locals()和globals()作为解析命名空间——这也是你在顶层直接调用pd.eval('[nan,1]')能正常运行的原因:回溯一层就能拿到__main__模块的命名空间,里面有你定义的nan。
但当你把pd.eval传入Series.apply时,pd.eval的直接调用方是C实现的map_infer循环。C编译的扩展函数在Python调用栈中没有对应的Python帧对象,pd.eval做栈回溯时碰到C层帧就无法继续向上访问,最终拿到的命名空间属于pandas内部模块,找不到你在__main__里定义的nan,解析表达式时就会抛出KeyError。
补充说明
你目前用的显式传入命名空间的方案是最稳妥的,一方面解决了作用域不匹配的问题,另一方面也能限制eval可访问的变量范围,避免执行任意代码带来的安全风险。
如果不想每次手动传参,套一层自定义Python函数/lambda调用eval也能解决问题,比如:
# 此时eval的直接调用方是你在__main__定义的lambda,能拿到__main__的命名空间 df['lst'] = df['lst'].apply(lambda x: eval(x))
这种写法能跑通,但因为没有限制eval的可访问范围,安全性不如显式传入可控命名空间的方案。
内容的提问来源于stack exchange,提问作者Ynjxsjmh
相关产品推荐
相关产品推荐

