Учебная страница курса биоинформатики,
год поступления 2011
Запуск внешних программ. Списочные сокращения.
Запуск внешних программ
В питоне, как и во многих других языках программирования, есть возможность запускать другие (внешние) программы прямо из питоновского кода. Для этого лучше всего использовать библиотеку subprocess (http://docs.python.org/2.7/library/subprocess.html).
call
Для того, чтобы просто запустить программу и узнать, с каким кодом она завершилась, лучше использовать функцию call:
1 >>> r=subprocess.call(["dir", "-l"])
2 total 2566
3 -rwx------+ 1 SYSTEM система 324 Feb 2 2011 ClearPluginsCache.cmd
4 drwx------+ 1 SYSTEM система 0 Sep 4 2012 Documentation
5 drwx------+ 1 SYSTEM система 0 Sep 4 2012 FExcept
6 -rwx------+ 1 SYSTEM система 1380352 Feb 2 2011 Far.exe
7 -rwx------+ 1 SYSTEM система 206129 Feb 2 2011 FarEng.hlf
8 -rwx------+ 1 SYSTEM система 36232 Feb 2 2011 FarEng.lng
9 -rwx------+ 1 SYSTEM система 331978 Feb 2 2011 FarRus.hlf
10 -rwx------+ 1 SYSTEM система 62704 Feb 2 2011 FarRus.lng
11 -rwx------+ 1 SYSTEM система 561 Feb 2 2011 File_id.diz
12 drwx------+ 1 SYSTEM система 0 Sep 4 2012 Plugins
13 -rwx------+ 1 SYSTEM система 772 Feb 2 2011 RestoreSettings.cmd
14 -rwx------+ 1 SYSTEM система 734 Feb 2 2011 SaveSettings.cmd
15 -rwx------+ 1 SYSTEM система 585638 Feb 2 2011 far.map
16 >>> print(r)
17 0
Первым аргументом передается список строк, первым элементом которого должно быть имя запускаемой программы, а остальные – аргументами этой программы (если они есть). Эта функция возвращает код, с которым завершилась программа (0 как правило означает, что программа завершилась корректно). По умолчанию, стандартные потоки ввода-вывода программы (STDIN, STDOUT и STDERR) перенаправляется из/в стандартные потоки родительского процесса (то есть питоновской программы). Если нужно перенаправить STDIN, STDOUT и/или STDERR в файл, то нужно передать файл в качестве аргументов stdin, stdout и/или stderr в функцию call:
check_call
Вторая похожая функция для запуска внешних программ – это check_call. Она отличается от функции call тем, что если программа возвращает код, не равный 0, она выбрасывает исключение, которое можно отлавливать:
Если запустить этот скрипт, на консоль распечатается следующее:
['dir', 'not_existing_dir'] 2 Command '['dir', 'not_existing_dir']' returned non-zero exit status 2
check_output
Еще одна похожая функция - check_output. В отличие от двух предыдущих функций, она возвращает не код, с которым завершилась программа, а выдачу этой программы.
При этом check_output, также как и check_call, выбрасывает исключение в случае некорректного завершения внешней программы.
Popen
Еще одна функция, которая может пригодиться в более экзотических ситуациях – это Popen. Эта функция (а точнее конструктор) позволяет запускать внешнюю программу и передавать ей на вход не только файл или то, что есть в STDIN родительского процесса (например, введенное через консоль), но и строчку, которая может быть, например, результатом работы другой программы (родительского скрипта или другой внешней программы). Для того, чтобы передать программе строчку в качестве входных данных, необходимо:
- 1) при создании объекта подпроцесса с помощью Popen указать, что stdin=subprocess.PIPE (это значит, что для подпроцесса открывается отдельный поток ввода); 2) передать строчку на вход процессу с помощью метода communicate. Этот метод возвращает кортеж (stdoutdata, stderrdata). stdoutdata содержит либо выдачу подпроцесса - внешней программы (если при создании подпроцесса было указано, что stdout=subprocess.PIPE), либо None. Аналогично с stderrdata. Например, вы хотите запустить программу infoseq из питоновской программы. При этом по каким-то причинам вы не хотите давать ей имя файла в качестве аргументов командной строки, а хотите ввести его потом - когда программа вас об этом попросит. В этом случае вам не удастся обойтись функциями call, check_call и check_output, а придется использовать Popen и communicate:
Аналогично, строка, передаваемая на вход одной программе, может быть выдачей другой программы. Вот несколько искусственный пример (работает только под Linux или под Windows с cygwin, т.к. использует программки grep и wc):
В этом примере программа grep находит в переданном файле все строчки, содержащие знак '>', которые в виде одной длинной строки (с переносами строк) попадают в переменную out1. Эту строчку обрабатывает программка wc -l, которая просто считает и возвращает количество строк. Таким образом, переменная out2 должна содержать количество строк во входном файле, содержащих знак '>'. В случае файла в формате fasta, это соответствует количеству последовательностей в этом файле. Но так можно делать только если передаваемая строка (out1) не очень большая и может влезть в оперативную память. В противном случае лучше создавать pipe между программами (как | в shell): выдачу одной программы напрямую передавать на вход другой программе:
Но в этом случае может возникнуть ситуация, когда вторая программа упала при том, что первая продолжает работать и посылать ей на вход свою выдачу. Поскольку вторая программа упала и эту выдачу не читает, первая программа заснет в ожидании выгребания своей выдаче. Что может быть фатально, если вы запускаете множество таких процессов. Чтобы этого не происходило, поток вывода второй программы нужно закрыть после завершения второй программы:
List comprehensions
Это небольшая темка про то, как можно одновременно ужать свой код и при этом сделать его более понятным. Однако синтаксическая конструкция, позволяющая это делать, позволяет также и сильно усложнить код. Поэтому сразу хочу призвать вас чувствовать меру в использовании списочный сокращения (list comprehensions) и использовать их только по делу.
Эта выразительная штука в питоне начала зарождаться очень давно. Где-то в начале-середине прошлого века сначала Эрнст Фрэнкель, а потом и Адольф Авраам Халеви Цермело занимались сочинением аксиоматики теории множеств и одна из их аксиом звучала так: если S – множество и p – предикат (т.е. функция, которая возвращает либо истину, либо ложь), то { x | x ∈ S, p(x) } – это тоже множество. Эту аксиому назвали аксиомой выделения, а по-английски the Axiom of Comprehension. Собственно, возможность писать такие конструкции { x | x ∈ S, p(x) } определяется в значительной степени этой аксиомой, поэтому эту нотацию стали называть (в некоторых узких областях математики) set comprehensions. А по-русски никак её не называют. Эта нотация множеств вдохновила авторов функциональных языков программирования сотворить подобную ей нотацию в своих языках. В функциональных языках эта нотация использовалась для преобразований списков, поэтому её стали называть list comprehensions. Одно из наиболее ярких последних творений на эту тему было в языке haskell, откуда, по его собственным словам, Гвидо и позаимствовал эту идею для питона3. По-русски "list comprehensions" можно называть разве что выделениями списков или списочными сокращениями.
Итак, в питоне можно писать такое: [ выражение for переменная in список ] и значить это будет следующее: для каждого элемента списка, указать на него этой переменной и вычислить в этом контексте выражение, собрать все результаты таких вычислений и положить снова в список.
Эта конструкция эквивалентна трём строкам питонского кода:
result = [] for x in [1, 2, 3]: result.append(x ** 2)
Знакомая конструкция, не правда ли? Теперь мы знаем, как это писать короче!
Сразу несколько примеров применения.
Мы хотим разобрать csv файл, в котором записаны числа. Подход первый, самый старый:
Тут мы сразу видим, что внутренний цикл можно представить в виде "выделения списка":
И снова упираемся в точно такую же конструкцию. Значит, и её можно свернуть:
Нельзя назвать эту запись самой очевидной, но зато она короче исходной в 4 раза (или вообще в 7 раз, смотря как считать).
Ещё один пример, связанный с разбором форматов. Предположим, мы хотим разобрать строку, в которой записан список присваиваний значений ключ=значение (и мы сразу требуем, чтобы внутри значений не было пробелов, и если значения повторяются, то мы будем использовать последнее). И, разумеется, мы хотим такую строку превратить в словарь.
Что может быть проще!
def parse_keys(line): return dict([word.split('=', 1) for word in line.split()])
line.split() – превратили строку в список слов. word.split("=", 1) – разбили слово по первому вхождению "=" (если никакого равенства в слове нет, нам вернётся список из одного элемента, и это вызовет ошибку, что хорошо; если есть больше одного равенства, то мы предполагаем, что второе равенство – это часть текста значения; split(..., 1) возвращает список длины не более 2). Полученный список (списков длины 2) превращаем в словарь и возвращаем.
Следюущий элемент сложности, который мы можем внести в list comprehensions: мы можем ходить не по одному списку, а по нескольким. Это будет эквивалентно нескольким вложенным циклам. Пример:
То есть эта конструкция эквивалентна четырём строкам:
Способ запомнить, в каком порядке случается обход: если в list comprehension воткнуть переносов строк, отступов и двоеточий, и убрать немного лишнего, то получатся вложенные циклы, обходящие списки ровно в том же порядке. Иными словами: первый for внешний (меняется реже всего), внутри него второй for (пробегает все значения для каждой итерации первого), внутри него третий, и так далее.
Наконец, в list comprehensions есть и ещё одна вещь, которую можно вставлять: проверки. Синтаксис столь же простой, как и раньше:
Эквивалентно:
Надеюсь, в этом месте не нужно никаких больше пояснений.
Как и for, if'ы можно добавлять в любом количестве, и их можно чередовать. Эффект будет таким же, как ровно в той же последовательности записанные вложенные for'ы и if'ы. В более вложенных конструкциях можно использовать переменные, определённые в более внешних (т.е. раньше по тексту).