使用Multiprocessing加速Pandas apply()时找不到函数的报错原因
哈哈,这个坑我之前刚踩过!别怀疑循环导入了,90%的概率是multiprocessing在Windows系统下的进程启动机制搞的鬼,跟我来一步步理清楚:
为什么会报这个错?
在Windows系统中,multiprocessing默认使用spawn方式创建子进程——它会重新启动一个Python解释器,然后重新导入你的主脚本模块。这时候如果你的test_func是定义在if __name__ == '__main__':代码块里面的,子进程导入模块时不会执行这个块(因为子进程的__name__不是__main__),自然就找不到这个函数了。
另外,就算函数不在主块里,如果你的代码结构导致pickle(multiprocessing用来传递函数/对象的序列化工具)无法正确序列化这个函数,也会出现类似的找不到属性的错误,但最常见的还是第一种情况。
怎么解决?
方案1:把函数移到if __name__ == '__main__':外面
这是最简单的解决办法,让子进程导入模块时能直接找到函数:
错误写法(会报错):
import pandas as pd from multiprocessing import Pool if __name__ == '__main__': def test_func(row): # 你的处理逻辑 return row['col1'] * 2 df = pd.DataFrame({'col1': [1,2,3,4]}) with Pool(4) as p: df['result'] = p.map(test_func, df.to_dict('records'))
正确写法:
import pandas as pd from multiprocessing import Pool # 把函数放在主块外面,子进程导入时能找到 def test_func(row): # 你的处理逻辑 return row['col1'] * 2 if __name__ == '__main__': df = pd.DataFrame({'col1': [1,2,3,4]}) with Pool(4) as p: df['result'] = p.map(test_func, df.to_dict('records'))
方案2:把函数放到单独的模块中
如果你的函数依赖主脚本里的一些复杂变量,或者想让代码更整洁,可以把函数抽离到单独的工具模块(比如my_functions.py):
my_functions.py内容:
def test_func(row): return row['col1'] * 2
主脚本内容:
import pandas as pd from multiprocessing import Pool from my_functions import test_func if __name__ == '__main__': df = pd.DataFrame({'col1': [1,2,3,4]}) with Pool(4) as p: df['result'] = p.map(test_func, df.to_dict('records'))
方案3:用更省心的第三方库(可选)
如果不想手动处理multiprocessing的细节,可以试试swifter库——它会自动判断你的函数适合用普通apply还是多进程加速,底层帮你处理好进程的问题:
import pandas as pd import swifter def test_func(row): return row['col1'] * 2 if __name__ == '__main__': df = pd.DataFrame({'col1': [1,2,3,4]}) df['result'] = df.swifter.apply(test_func, axis=1)
补充说明
如果是在Linux/macOS系统下,默认用fork方式创建子进程,它会直接复制父进程的内存空间,所以就算函数在主块里也不会报错——这也是为什么很多教程里的代码在Linux下能跑,Windows下就炸的原因。
内容的提问来源于stack exchange,提问作者OverflowingTheGlass

