Предположим, у вас есть файл, пусть он называется udfs.py
и в нем:
def nested_f(x):
return x + 1
def main_f(x):
return nested_f(x) + 1
Затем вы хотите сделать UDF из функции main_f
и запустить его на фрейме данных:
import pyspark.sql.functions as fn
import pandas as pd
pdf = pd.DataFrame([[1], [2], [3]], columns=['x'])
df = spark.createDataFrame(pdf)
_udf = fn.udf(main_f, 'int')
df.withColumn('x1', _udf(df['x'])).show()
Это работает нормально, если мы делаем это из одного и того же файла, где определены две функции (udfs.py
). Однако попытка сделать это из другого файла (скажем, main.py
) приводит к ошибке ModuleNotFoundError: No module named...
:
...
import udfs
_udf = fn.udf(udfs.main_f, 'int')
df.withColumn('x1', _udf(df['x'])).show()
Я заметил, что если я на самом деле nested_f
в main_f
следующим образом:
def main_f(x):
def nested_f(x):
return x + 1
return nested_f(x) + 1
все работает нормально. Однако моя цель здесь состоит в том, чтобы логика была хорошо разделена на несколько функций, которые я также могу проверить по отдельности.
Я думаю, что это можно решить, udfs.py
файл udfs.py
(или целую zip-папку) исполнителям, используя spark.sparkContext.addPyFile('...udfs.py')
. Тем не мение:
udfs.py
может использовать множество других модулей, которые затем также должны быть отправлены, что приводит к некоторой цепной реакции...)addPyFile
(например, автозагрузка может перестать работать и т.д.)Таким образом, вопрос: есть ли способ сделать все это одновременно:
addPyFile
Бонусные баллы за разъяснение, как это работает/почему это не работает!
Для небольших (один или два локальных файла) зависимостей вы можете использовать --py файлы и перечислять их с чем-то большим или большим количеством зависимостей - лучше упаковать их в файл zip или egg.
Файл udfs.py
:
def my_function(*args, **kwargs):
# code
Файл main.py
:
from pyspark import SparkContext
from pyspark.sql import SparkSession
from pyspark.sql.functions import udf
from udfs import my_function
sc = SparkContext()
spark = SparkSession(sc)
my_udf = udf(my_function)
df = spark.createDataFrame([(1, "a"), (2, "b")])
df.withColumn("my_f", my_udf("..."))
Для бега:
pyspark --py-files /path/to/udfs.py
# or
spark-submit --py-files /path/to/udfs.py main.py
Если вы написали свой собственный модуль Python или даже сторонние модули (которые не нуждаются в компиляции C), мне лично это понадобилось с помощью geoip2
, лучше создать файл zip или egg.
# pip with -t install all modules and dependencies in directory 'src'
pip install geoip2 -t ./src
# Or from local directory
pip install ./my_module -t ./src
# Best is
pip install -r requirements.txt -t ./src
# If you need add some additionals files
cp ./some_scripts/* ./src/
# And pack it
cd ./src
zip -r ../libs.zip .
cd ..
pyspark --py-files libs.zip
spark-submit --py-files libs.zip
Будьте осторожны при использовании pyspark --master yarn
(возможно, с другими нелокальными параметрами мастера) в оболочке --py-files
с --py-files
:
>>> import sys
>>> sys.path.insert(0, '/path/to/libs.zip') # You can use relative path: .insert(0, 'libs.zip')
>>> import MyModule # libs.zip/MyModule
РЕДАКТИРОВАТЬ - Ответ на вопрос, как получить функции на исполнителях без addPyFile()
и --py-files
:
Необходимо иметь данный файл с функциями по отдельным исполнителям. И достижимый через ПУТЬ env. Поэтому я бы, вероятно, написал модуль Python, который я затем установил на исполнителей и был доступен в среде.
--py-files
, похоже, что --py-files
- это просто CLI-эквивалент addPyFile
( stackoverflow.com/a/38072930/1913724 ). Может случиться так, что то, о чем я прошу, не существует, и в этом случае было бы неплохо узнать почему!
addPyFile ()
и --py-files
: необходимо иметь данный файл с функциями для отдельных исполнителей. И достижимый через ПУТЬ env. Поэтому я бы, вероятно, написал модуль Python, который я затем установил на исполнителей и был доступен в среде.