从外部范围访问 pyspark 用户定义函数中的对象,避免 PicklingError: Could not serialize object

Rus*_*rdt 7 python serialization user-defined-functions apache-spark pyspark

如何避免在pyspark用户定义函数中初始化类?这是一个例子。

创建一个spark会话和代表四个纬度和经度的 DataFrame。

import pandas as pd
from pyspark import SparkConf
from pyspark.sql import SparkSession

conf = SparkConf()
conf.set('spark.sql.execution.arrow.pyspark.enabled', 'true')
spark = SparkSession.builder.config(conf=conf).getOrCreate()

sdf = spark.createDataFrame(pd.DataFrame({
    'lat': [37, 42, 35, -22],
    'lng': [-113, -107, 127, 34]}))
Run Code Online (Sandbox Code Playgroud)

这是 Spark 数据框

+---+----+
|lat| lng|
+---+----+
| 37|-113|
| 42|-107|
| 35| 127|
|-22|  34|
+---+----+
Run Code Online (Sandbox Code Playgroud)

通过包在每个纬度/经度处使用时区字符串丰富 DataFrame timezonefinder下面的代码运行没有错误

from typing import Iterator
from timezonefinder import TimezoneFinder

def func(iterator: Iterator[pd.DataFrame]) -> Iterator[pd.DataFrame]:
    for dx in iterator:
        tzf = TimezoneFinder()
        dx['timezone'] = [tzf.timezone_at(lng=a, lat=b) for a, b in zip(dx['lng'], dx['lat'])]
        yield dx
pdf = sdf.mapInPandas(func, schema='lat double, lng double, timezone string').toPandas()
Run Code Online (Sandbox Code Playgroud)

上面的代码运行没有错误,并创建了下面的 pandas DataFrame。问题是TimezoneFinder类是在用户定义的函数中初始化的,这会产生瓶颈

In [4]: pdf
Out[4]:
    lat    lng         timezone
0  37.0 -113.0  America/Phoenix
1  42.0 -107.0   America/Denver
2  35.0  127.0       Asia/Seoul
3 -22.0   34.0    Africa/Maputo
Run Code Online (Sandbox Code Playgroud)

问题是如何让这段代码更像下面这样运行,其中TimezoneFinder类在用户定义的函数之外初始化一次。事实上,下面的代码会生成此错误PicklingError: Could not serialize object: TypeError: cannot pickle '_io.BufferedReader' object

def func(iterator: Iterator[pd.DataFrame]) -> Iterator[pd.DataFrame]:
    for dx in iterator:
        dx['timezone'] = [tzf.timezone_at(lng=a, lat=b) for a, b in zip(dx['lng'], dx['lat'])]
        yield dx
tzf = TimezoneFinder()
pdf = sdf.mapInPandas(func, schema='lat double, lng double, timezone string').toPandas()
Run Code Online (Sandbox Code Playgroud)

更新 - 还尝试使用functools.partial外部函数,但仍然收到相同的错误。也就是说,这种方法不起作用:

def outer(iterator, tzf):
    def func(iterator: Iterator[pd.DataFrame]) -> Iterator[pd.DataFrame]:
        for dx in iterator:
            dx['timezone'] = [tzf.timezone_at(lng=a, lat=b) for a, b in zip(dx['lng'], dx['lat'])]
            yield dx
    return func(iterator)
tzf = TimezoneFinder()
outer = partial(outer, tzf=tzf)
pdf = sdf.mapInPandas(outer, schema='lat double, lng double, timezone string').toPandas()
Run Code Online (Sandbox Code Playgroud)

mdu*_*ant 5

您将需要每个工作线程上都有一个对象的缓存实例。你可以这样做

instance = [None]

def func(iterator: Iterator[pd.DataFrame]) -> Iterator[pd.DataFrame]:
    if instance[0] is None:
        instance[0] = TimezoneFinder()
    tzf = instance[0]
    for dx in iterator:
        dx['timezone'] = [tzf.timezone_at(lng=a, lat=b) for a, b in zip(dx['lng'], dx['lat'])]
        yield dx
Run Code Online (Sandbox Code Playgroud)

请注意,要实现此功能,您的函数将在模块内定义,以便为实例缓存提供存放的地方。否则,您将不得不将其挂在某些内置模块上,例如os.instance = [].

  • 仅当此代码动态执行时才需要,例如在笔记本单元而不是模块中。 (2认同)