( источник изображения )
Вступление
Значит, тебе нравится свинья, но она стесняет твой стиль? Вы не уверены, о чем идет речь? Вы хотите написать какой-то код, чтобы написать код для вас? Если да, то это для вас.
Этот учебник связывает воедино множество различных техник и технологий. Цель здесь состоит в том, чтобы показать вам трюк, чтобы заставить свинью вести себя так, чтобы это было немного более запутанным. Это трюк, который я использовал раньше довольно часто, и я написал пару полезных функций, чтобы упростить его. Я пройдусь по кусочкам здесь. Этот учебник, в более общем плане, посвящен написанию кода, который пишет код. Общие методы и проблемы, описанные здесь, могут быть применены к другим проблемам генерации кода.
Что Делает Свинья?
Pig-это набор инструментов сценариев высокого уровня, используемый для определения и выполнения сложных рабочих процессов сокращения карт. Давайте подробнее рассмотрим это предложение…
Свинья, это проект Apache верхнего уровня. Он с открытым исходным кодом и действительно довольно изящный. Подробнее об этом здесь . Свинья латынь-это язык Свиньи. Pig выполняет сценарии PigLatin. В скрипте PigLatin вы пишете кучу операторов, которые преобразуются в кучу заданий mapreduce, которые могут выполняться последовательно в вашем кластере Hadoop. Обычно приятно абстрагироваться от написания простых старых заданий mapreduce, потому что они могут быть полной болью в шее.
Если вы раньше не использовали Pig и не уверены, что это для вас, было бы неплохо проверить Hive. Улей и Свинья имеют много совпадений с точки зрения функциональности, но имеют разные философии. Они не являются абсолютными конкурентами, потому что часто используются в сочетании друг с другом. Улей напоминает SQL, в то время как ПигЛатин напоминает… ПигЛатин. Так что, если вы знакомы с SQL, то Hive может быть легче изучить, но я надеюсь, что это немного более разумно, чем дать представление о том, как он описывает поток данных.
Чего Не Делает Свинья?
Pig не принимает никаких решений о потоке выполнения программы, он только позволяет вам указать поток данных. Другими словами, это позволяет вам говорить такие вещи, как это:
----------------------------------------------- -- define some data format goodies ----------------------------------------------- define CSV_READER org.apache.pig.piggybank.storage.CSVExcelStorage( ',', 'YES_MULTILINE', 'UNIX' ); define CSV_WRITER org.apache.pig.piggybank.storage.CSVExcelStorage( ',', 'YES_MULTILINE', 'UNIX', 'SKIP_OUTPUT_HEADER' ); ----------------------------------------------- -- load some data ----------------------------------------------- r_one = LOAD 'one.csv' using CSV_READER AS (a:chararray,b:chararray,c:chararray); r_two = LOAD 'two.csv' using CSV_READER AS (a:chararray,d:chararray,e:chararray); ----------------------------------------------- -- do some processing ----------------------------------------------- r_joined = JOIN r_one by a, t_two by a; r_final = FOREACH r_joined GENERATE r_one::a, b, e; ----------------------------------------------- -- store the result ----------------------------------------------- store r_final into 'r_three.csv' using CSV_WRITER;
В приведенном выше сценарии указано, куда должны поступать данные. Каждый оператор, который вы там видите, будет выполнен ровно один раз, несмотря ни на что (если только не будет какой-то ошибки).
Вы можете запустить скрипт из командной строки следующим образом:
pig path/to/my_script.oink
Хорошо, а что, если у нас есть куча файлов, и с каждым из них должно произойти одно и то же? Означает ли это, что нам нужно будет скопировать и вставить ваш латинский шрифт Свиньи и отредактировать каждый из них, чтобы иметь правильные пути?
Ну, нет. Свинья допускает некоторые действительно базовые замены. Вы можете делать такие вещи, как это:
r_one = LOAD '$DIR/one.csv' using CSV_READER AS (a:chararray,b:chararray,c:chararray); r_two = LOAD '$DIR/two.csv' using CSV_READER AS (a:chararray,d:chararray,e:chararray); ----------------------------------------------- -- do some processing ----------------------------------------------- r_joined = JOIN r_one by a, t_two by a; r_final = FOREACH r_joined GENERATE r_one::a, b, e; ----------------------------------------------- -- store the result ----------------------------------------------- store r_final into '$DIR/r_three.csv' using CSV_WRITER;
Затем вы можете запускать скрипт столько раз, сколько захотите, с разными значениями для DIR
. Что-то вроде:
pig path/to/my_script.oink -p DIR=jan_2015 pig path/to/my_script.oink -p DIR=feb_2015 pig path/to/my_script.oink -p DIR=march_2015
Таким образом, pig допускает замену переменных, и это довольно мощная вещь сама по себе. Но он не допускает циклов или операторов if, и это может быть несколько ограничивающим. Что, если бы нам пришлось перебирать более 60 различных значений для DIR
? Это то, что Свинья не обслуживает.
К счастью для нас, Python может отлично петлять. Так что мы могли бы сделать что-то вроде:
def run_pig_script(sFilePath,dPigArgs=None): """ run piggy run """ import subprocess lCmd = ["pig",sFilePath,] for sArg in ['{0}={1}'.format(*t) for t in (dPigArgs or {}).items()]: lCmd.append('-p') lCmd.append(sArg) print lCmd p = subprocess.Popen(lCmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) stdout, stderr = p.communicate() return stdout,stderr for sDir in lManyDirectories: run_pig_script(sFilePath="path/to/my_script.oink",dPigArgs={'DIR':sDir})
Функция run_pig_script
использует модуль подпроцесс
для создания процесса Pig с помощью функции Popen
. Popen
принимает список строк токенов в качестве своего первого аргумента и оттуда выполняет системный вызов. Поэтому сначала мы создаем список команд cmd
, а затем запускаем процесс. Вывод процесса (материал, который обычно печатается в окне консоли) перенаправляется на объекты stderr и stdout.
Для заполнения lCmd
мы используем сокращенное обозначение цикла, известное как понимание списка. Это очень круто и полезно, но выходит за рамки этого текста. Попробуйте вызвать run_pig_script
с несколькими различными аргументами и посмотреть, что он печатает, и вы легко поймете, что ожидает Popen
.
Но что, если вам действительно нужен цикл внутри вашего сценария свиньи?
Итак, мы рассмотрели выполнение латинского сценария Pig много раз с разными значениями, что делать, если мы хотим использовать много переменных в сценарии PigLatin? Например, что произойдет, если мы захотим перебрать некоторое переменное количество каталогов в одном скрипте? Например, что-то вроде этого…
r_jan_1 = LOAD 'jan_1/sales.csv' USING CSV_READER AS (a,b,c,d,e,f,g); r_jan_2 = LOAD 'jan_2/sales.csv' USING CSV_READER AS (a,b,c,d,e,f,g); r_jan_3 = LOAD 'jan_3/sales.csv' USING CSV_READER AS (a,b,c,d,e,f,g); r_jan_4 = LOAD 'jan_4/sales.csv' USING CSV_READER AS (a,b,c,d,e,f,g); ... more stuff r_jan_16 = LOAD 'jan_16/prices.csv' USING CSV_READER AS (a,b,c,d,e,f,g); r_all = UNION r_jan_1, r_jan_2, r_jan_3, r_jan_4, ... r_jan_16;
Записывать все это может стать утомительным. Особенно, если мы каждый раз работаем с произвольным количеством файлов. Может быть, мы хотим объединить все продажи месяца до сих пор, тогда нам нужно будет придумать новый сценарий на каждый день. Это звучит довольно ужасно и потребует много копипаста, а копипаста плохо пахнет.
Итак, Вот Что Мы собираемся сделать Вместо этого
Есть какой-то питоновский псевдокод:
lStrs = complicated_operation_getting_list_of_strings() #1 sPigPath = generate_pig_script(lStrs) #2 run_pig_script(sFilePath = sPigPath) #3
Итак, у нас есть 3 шага в приведенном выше коде: Шаг 1-получение необходимых нам данных, на которые будет полагаться сценарий pig. Затем, на шаге 2, нам нужно взять эти данные и превратить их во что-то, что Свинья сможет понять. Затем шаг 3 должен заставить его работать.
Шаг 1 процесса очень сильно зависит от того, что вы пытаетесь сделать. Следуя из предыдущего примера, мы, вероятно, хотели бы, чтобы complicated_operation_getting_list_of_strings
выглядело так:
def complicated_operation_getting_list_of_strings(): import datetime oNow = datetime.datetime.now() sMonth = oNow.strftime('%b').lower() return ["{0}_{1}".format(sMonth,i+1) for i in range(oNow.day)]
Остальная часть этого урока будет посвящена шагам 2 и 3.
Системы Шаблонов
Написание кода, чтобы написать код для нас! Это довольно футуристическая штука!
Не совсем…
Вы когда-нибудь писали веб-приложение? Вы использовали для этого какой-то фреймворк? Указал ли фреймворк (или позволил вам указать) какой-то особый способ написания HTML, чтобы вы могли делать умные вещи в своих HTML-файлах? Умные вещи, такие как циклы, “если” и подстановки переменных? Если вы ответили ” да ” на эти вопросы, вы написали код, который, по крайней мере, написал HTML-код для вас. И если вы ответили “нет”, то сообщение на вынос здесь таково: написание кода, который пишет код,-это то, что делалось веками, есть много системных библиотек и пакетов, которые поддерживают подобные вещи на многих языках. Такие инструменты обычно называются системами шаблонов.
Система шаблонов, которую мы будем использовать для этого, – это Мако. Это не учебник по мако, чтобы узнать о мако, проверьте это .
При выборе системы шаблонов важно убедиться, что она не конфликтует с языком, на котором вы ее пишете. И если это произойдет, вам нужно найти способы компенсировать это. Я имею в виду следующее: если я использую язык шаблонов, то этот язык имеет несколько четко определенных управляющих последовательностей для выполнения таких вещей, как циклы и подстановка переменных. Примером из мако является:
${xSomeVariable}
Когда вы визуализируете эту строку кода, значение somevariable
превратится в строку. Но что, если ${stuff}
означало что-то на языке, который вы пытаетесь создать? Тогда есть хороший шанс, что mako найдет в ваших файлах шаблонов вещи, с которыми, по его мнению, ему нужно иметь дело, и он либо выведет мусор, либо вызовет исключения.
У Мако и Пиглатина нет этой проблемы. Так что это довольно удобно.
Использование Python для создания Pig Latin
Помните это: sPigPath(lNames)
?
Хорошие программисты не смешивают языки в одном файле, если они могут помочь (что почти всегда). Поэтому, хотя можно определить весь ваш шаблон Pig Latin mako в виде большой гигантской строки внутри вашего скрипта Python, мы не собираемся этого делать.
Кроме того, было бы неплохо, если бы код, который мы пишем, работал для более чем одного шаблона. Так что вместо:
sPigPath = generate_pig_script(lStrs) #2
Мы сделаем это:
sPigPath = generate_pig_script(sFilePath,dContext) #2
Мы хотим передать путь к нашему файлу шаблона вместе со словарем, содержащим переменные контекста, которые мы будем использовать для его визуализации на этот раз. Например, мы могли бы:
dContext = { 'lStrs' : complicated_operation_getting_list_of_strings() }
Хорошо, тогда давайте напишем какой-нибудь реальный код…
def generate_pig_script(sFilePath,dContext): """ render the template at sFilePath using the context in dContext, save the output in a temporary file return the path to the generated file """ from mako.template import Template import datetime #1. fetch the template from the file oTemplate = Template(filename=sFilePath) #2. render it using the context dictionary. This gives us a string sOutputScript = oTemplate.render(**dContext) #3. put the output into some file... sOutPath = "{0}_{1}".format(sFilePath,datetime.datetime.now().isoformat()) with open(sOutPath,'w') as f: f.write(sOutputScript) return sOutPath
Комментариев в коде должно быть достаточно, чтобы понять его общее функционирование.
Просто чтобы завершить картину, давайте сделаем реальный шаблон…
Помнишь это?
r_jan_1 = LOAD 'jan_1/sales.csv' USING CSV_READER AS (a,b,c,d,e,f,g); r_jan_2 = LOAD 'jan_2/sales.csv' USING CSV_READER AS (a,b,c,d,e,f,g); r_jan_3 = LOAD 'jan_3/sales.csv' USING CSV_READER AS (a,b,c,d,e,f,g); r_jan_4 = LOAD 'jan_4/sales.csv' USING CSV_READER AS (a,b,c,d,e,f,g); ... more stuff r_jan_16 = LOAD 'jan_16/prices.csv' USING CSV_READER AS (a,b,c,d,e,f,g); r_all = UNION r_jan_1, r_jan_2, r_jan_3, r_jan_4, ... r_jan_16;
Вот он в виде шаблона мако:
%for sD in lStrs: r_${sD} = LOAD '${sD}/sales.csv' USING CSV_READER AS (a,b,c,d,e,f,g); %endfor r_all = UNION ${','.join(['r_{0}'.format(sD) for sD in lStrs])};
Полная картина
Итак, теперь мы использовали Python для создания сценария PigLatin и хранения его в известном месте. И мы уже знаем, как заставить Python запустить Pig. Так вот оно что. Довольно прямолинейно, а? В этом уроке использовалось несколько различных технологий и техник, и невозможно немного не прыгать, поэтому я включил здесь небольшое резюме о том, как использовать эту технику:
#1 given a working PigLatin script that has a lot of repitition or a variable number of inputs, create a mako template #2 write a function that creates the context for the mako template. eg: dContext = { 'lStrs' : complicated_operation_getting_list_of_strings() } #3 render the template sPigFilePath = generate_pig_script(sMakoFilePath,dContext) #and finally run the thing... run_pig_script(sPigFilePath,dPigArgs=None)
Вывод
Мы рассмотрели некоторые основы генерации кода и использовали Python и систему шаблонов mako, чтобы сделать Pig более зацикленным. Я затронул множество различных технологий и техник. Свинья сама по себе довольно большое дело, и виды проблем, к которым она применяется, могут заполнить книги. Механизм шаблонов mako сам по себе является мощной вещью и имеет много вариантов использования, кроме Pig (я в основном использую его в сочетании с Pyramid, например). Циклы Python и понимание списков стоит изучить, если какие-либо странные вещи для цикла не имели смысла; и, наконец, модуль подпроцесса – он сам по себе представляет собой кроличью нору.