使用PySpark从地图创建全局列表的问题

时间:2015-08-17 17:35:21

标签: python apache-spark pyspark

我有这段代码,我正在使用ipythonpyspark中阅读文件。我要做的是添加一个片段,它根据从文件中读取的特定列形成一个列表但是当我尝试执行它时,列表变为空,并且没有任何内容被附加到它。我的代码是:

list1 = []

def file_read(line):

    list1.append(line[10])
    # bunch of other code which process other column indexes on `line`

inputData = sc.textFile(fileName).zipWithIndex().filter(lambda (line,rownum): rownum>0).map(lambda (line, rownum): line)

column_val = (inputData
    .map(lambda line: line.split(","))
    .filter(lambda line: len(line) >1 )
    .map(file_read))

当我执行这部分代码时list1仍然是空的,即使line[10]中有数据,因为我在上面相同函数的代码的其他部分中使用它。好像它只是没有将它附加到列表中。我如何形成上面的列表?

1 个答案:

答案 0 :(得分:6)

嗯,它实际上附加到list1,问题不在于您正在考虑的问题。闭包中引用的每个变量都被序列化并发送给工作者。它也适用于list1

每个分区都会收到它自己的list1副本,当调用file_read时,数据会附加到此副本,当给定的映射阶段完成时,它会超出范围并被丢弃。< / p>

不是特别优雅的代码,但你应该看到它真的发生在这里:

rdd = sc.parallelize(range(100), 5)

line1 = []

def file_read(line):
    list1.append(line)
    print len(list1)
    return line

xs = rdd.map(file_read).collect()

修改

Spark提供两种类型的共享变量。 Broadcast variables,只能从worker透视图中读取,而accumulators只能从驱动程序的角度编写。

默认情况下,accumulators仅支持数字变量,主要用作计数器。但是可以定义自定义累加器。为此,您必须扩展AccumulatorParam课程并提供自定义zeroaddInPlace实施:

class ListParam(AccumulatorParam):
    def zero(self, v):
        return []
    def addInPlace(self, acc1, acc2):
        acc1.extend(acc2)
        return acc1

接下来,您可以按如下方式重新定义file_read

def file_read1(line):
    global list1 # Required otherwise the next line will fail
    list1 += [line]
    return line

使用示例:

list1 = sc.accumulator([], ListParam())

rdd = sc.parallelize(range(10)).map(file_read1).collect()
list1.value

即使可以使用这样的累加器,在实践中使用也可能是昂贵的,并且在最坏的情况下它可能会使驱动器崩溃。相反,您可以简单地使用另一种转换:

tmp = (inputData
    .map(lambda line: line.split(","))
    .filter(lambda line: len(line) >1 ))

def line_read2(line): return ... # Just a core logic

line1 = tmp.map(lambda line: line[10])
column_val = tmp.map(line_read2)

旁注

您提供的代码没有做任何事情。 Spark中的转换只是对必须完成的操作的描述,但在调用操作数据之前,实际上并没有执行任何操作。