Вызов другой пользовательской функции Python из UDF Pyspark

1

Предположим, у вас есть файл, пусть он называется 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'). Тем не мение:

  1. Я нахожу это немного скучным (особенно если вам нужно архивировать папки и т.д.)
  2. Это не всегда легко/возможно (например, udfs.py может использовать множество других модулей, которые затем также должны быть отправлены, что приводит к некоторой цепной реакции...)
  3. Есть и другие неудобства с addPyFile (например, автозагрузка может перестать работать и т.д.)

Таким образом, вопрос: есть ли способ сделать все это одновременно:

  • иметь логику UDF, красиво разделенную на несколько функций Python
  • использовать UDF из файла, отличного от того, где определена логика
  • не нужно отправлять какие-либо зависимости, используя addPyFile

Бонусные баллы за разъяснение, как это работает/почему это не работает!

  • 0
    Зарегистрируйте свою функцию как UDF в самом файле udfs.py.
  • 0
    Вы пробовали это? Я не думаю, что это работает.
Показать ещё 1 комментарий
Теги:
apache-spark
pyspark
user-defined-functions

1 ответ

1

Для небольших (один или два локальных файла) зависимостей вы можете использовать --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, который я затем установил на исполнителей и был доступен в среде.

  • 0
    Спасибо, полезный ответ, хотя это не совсем то, что мне --py-files , похоже, что --py-files - это просто CLI-эквивалент addPyFile ( stackoverflow.com/a/38072930/1913724 ). Может случиться так, что то, о чем я прошу, не существует, и в этом случае было бы неплохо узнать почему!
  • 0
    @Ferrard - ответ на вопрос о том, как получить функции для исполнителей без addPyFile () и --py-files : необходимо иметь данный файл с функциями для отдельных исполнителей. И достижимый через ПУТЬ env. Поэтому я бы, вероятно, написал модуль Python, который я затем установил на исполнителей и был доступен в среде.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню