我已经在这个在线jupyter笔记本https://tmpnb.org/上玩Spark和Python,并尝试了3种传递python函数的方法:
1)使用地图
import numpy as np
def my_sqrt(x):
return np.sqrt(x)
sc.parallelize(range(10)).map(my_sqrt).collect()
2)并行化 my_sqrt 并将其命名为
sc.parallelize([(my_sqrt, i) for i in range(10)]).map(lambda x : x[0](x[1])).collect()
3)并行化 np.sqrt 并将其命名为
sc.parallelize([(np.sqrt, i) for i in range(10)]).map(lambda x : x[0](x[1])).collect()
(1)和(3)做工作,(2)不做。首先,我想了解为什么/如何(1)和(3)工作。其次,我想了解为什么(2)没有,以及可以做些什么才能使它发挥作用。
答案 0 :(得分:6)
第一个方法有效,因为Spark正在使用特殊的序列化策略来处理转换所需的闭包,这种闭包明显比标准pickle
慢但功能更强大(否则我们无法做到)使用.map(lambda x: ...)
)。
最后一个方法有效,因为根本不需要序列化功能代码。它引用sqrt
模块中的numpy
,因此只要每个工作人员都可以访问NumPy,就没有问题。
第二种方法不起作用,因为酸洗不会对代码进行序列化。
import pickle
pickle.dumps(my_sqrt)
## b'\x80\x03c__main__\nmy_sqrt\nq\x00.'
所有这一切都说明请从顶级脚本环境中给我一个分配给my_sqrt
(my_sqrt.__name__
)的对象(又名__main__
)。当它在工作人员上执行时,它不会使用相同的环境,并且范围内不再有这样的对象,因此例外。要清楚它既不是一个bug,也不是特定于Spark的东西。您可以在本地轻松地重现相同的行为,如下所示:
In [1]: import pickle
In [2]: def foo(): ...
In [3]: foo_ = pickle.dumps(foo)
In [4]: pickle.loads(foo_)
Out[4]: <function __main__.foo>
In [5]: del foo
In [6]: pickle.loads(foo_)
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
...
AttributeError: Can't get attribute 'foo' on <module '__main__'>
由于它并不关心实际价值,你甚至可以像这样重新分配:
In [7]: foo = "foo"
In [8]: pickle.loads(foo_)
Out[8]: 'foo'
带走消息这里是如果你想使用一个函数这样把它放在一个单独的模块中,并以与你对其他依赖项相同的方式在工作者之间分发它,包括自定义类定义