Python предлагает несколько вариантов запуска внешних процессов и взаимодействия с операционной системой. Однако методы отличаются для Python 2 и 3. Python 2 имеет несколько методов в модуле os, которые теперь устарели и заменены модулем subprocess, который является предпочтительным вариантом в Python 3.
В этой статье мы поговорим о различных методах ОС и подпроцессов, о том, как их использовать, чем они отличаются друг от друга, в какой версии Python их следует использовать и даже о том, как преобразовать старые команды в новые единицы.
Надеюсь, к концу этой статьи вы лучше поймете, как вызывать внешние команды из кода Python и какой метод следует использовать для этого. Прежде всего, это старые методы os.popen *.
Методы os.popen *
Модуль os предлагает четыре различных метода, которые позволяют нам взаимодействовать с операционной системой (точно так же, как с командной строкой) и создавать канал для других команд. Я имею в виду следующие методы: popen, popen2, popen3 и popen4, все из которых описаны в следующих разделах.
Цель каждого из этих методов – иметь возможность вызывать другие программы из вашего кода в Python. Это может быть вызов другого исполняемого файла, например вашей собственной скомпилированной программы на C++, или команды оболочки, например ls или mkdir.
os.popen
Метод os.popen открывает канал из команды. Этот канал позволяет команде отправлять свои выходные данные другой команде. Результатом является открытый файл, к которому могут получить доступ другие программы.
Синтаксис следующий:
os.popen(command[, mode[, bufsize]])
Здесь параметр команды – это то, что вы будете выполнять, и его вывод будет доступен через открытый файл. Режим аргумента определяет, доступен ли этот выходной файл для чтения (‘r’) или записи (‘w’). Добавление «b» к режиму откроет файл в двоичном режиме. Таким образом, например, «rb» создаст читаемый объект двоичного файла.
Чтобы получить код выхода выполненной команды, вы должны использовать метод close() файлового объекта.
Параметр bufsize сообщает popen, сколько данных следует буферизовать, и может принимать одно из следующих значений:
- 0 = без буферизации (значение по умолчанию);
- 1 = строчная буферизация;
- N = приблизительный размер буфера, когда N> 0; и значение по умолчанию, когда N <0.
Этот метод доступен для платформ Unix и Windows и устарел, начиная с версии Python 2.6. Если вы в настоящее время используете этот метод и хотите переключиться на версию Python 3, вот эквивалентная версия подпроцесса для Python 3:
| Методика | Заменяется |
|---|---|
| pipe = os.popen (‘cmd’, ‘r’, bufsize) | pipe = Popen (‘cmd’, shell = True, bufsize = bufsize, stdout = PIPE) .stdout. |
| pipe = os.popen (‘cmd’, ‘w’, bufsize) | pipe = Popen (‘cmd’, shell = True, bufsize = bufsize, stdin = PIPE) .stdin. |
В приведенном ниже коде показан пример использования метода os.popen:
import os
p = os.popen('ls -la')
print(p.read())
Приведенный выше код попросит операционную систему перечислить все файлы в текущем каталоге. Результатом нашего метода, который хранится в p, является открытый файл, который читается и печатается в последней строке кода. Результат этого кода (в контексте моего текущего каталога) выглядит следующим образом:
$ python popen_test.py total 32 drwxr-xr-x 7 scott staff 238 Nov 9 09:13 . drwxr-xr-x 29 scott staff 986 Nov 9 09:08 .. -rw-r--r-- 1 scott staff 52 Nov 9 09:13 popen2_test.py -rw-r--r-- 1 scott staff 55 Nov 9 09:14 popen3_test.py -rw-r--r-- 1 scott staff 53 Nov 9 09:14 popen4_test.py -rw-r--r-- 1 scott staff 49 Nov 9 09:13 popen_test.py -rw-r--r-- 1 scott staff 0 Nov 9 09:13 subprocess_popen_test.py
os.popen2
Этот метод очень похож на предыдущий. Основное различие заключается в том, что выводит метод. В этом случае он возвращает два файловых объекта, один для стандартного ввода, а другой файл для стандартного вывода.
Синтаксис следующий:
popen2(cmd[, mode[, bufsize]])
Эти аргументы имеют то же значение, что и в предыдущем методе os.popen.
Метод popen2 доступен как для платформ Unix, так и для Windows. Однако он присутствует только в Python 2. Опять же, если вы хотите использовать вместо этого версию подпроцесса (более подробно показанную ниже), используйте вместо этого следующее:
| Методика | Заменяется |
|---|---|
| (child_stdin, child_stdout) = os.popen2 (‘cmd’, mode, bufsize) | p = Popen (‘cmd’, shell = True, bufsize = bufsize, stdin = PIPE, stdout = PIPE, close_fds = True) (child_stdin, child_stdout) = (p.stdin, p.stdout) |
В приведенном ниже коде показан пример использования этого метода:
import os
in, out = os.popen2('ls -la')
print(out.read())
Этот код даст те же результаты, что и в первом выводе кода выше. Разница здесь в том, что вывод метода popen2 состоит из двух файлов. Таким образом, вторая строка кода определяет две переменные: in и out. В последней строке мы читаем выходной файл и выводим его на консоль.
os.popen3
Этот метод очень похож на предыдущие. Однако разница в том, что вывод команды представляет собой набор из трех файлов: stdin, stdout и stderr.
Синтаксис:
os.popen3(cmd[, mode[, bufsize]])
Где, аргументы cmd, mode и bufsize имеют те же характеристики, что и в предыдущих методах. Метод доступен для платформ Unix и Windows.
Обратите внимание, что этот метод устарел, и документация в Python рекомендует нам заменить метод popen3 следующим образом:
| Методика | Заменяется |
|---|---|
| (child_stdin, child_stdout, child_stderr) = os.popen3 (‘cmd’, mode, bufsize) | p = Popen (‘cmd’, shell = True, bufsize = bufsize, stdin = PIPE, stdout = PIPE, stderr = PIPE, close_fds = True) (child_stdin, child_stdout, child_stderr) = (p.stdin, p.stdout, p .stderr) |
Как и в предыдущих примерах, приведенный ниже код даст тот же результат, что и в нашем первом примере.
import os
in, out, err = os.popen3('ls -la')
print(out.read())
Однако в этом случае мы должны определить три файла: stdin, stdout и stderr. Список файлов из нашей команды ls-la сохраняется в файле out.
os.popen4
Как вы, наверное, догадались, метод os.popen4 похож на предыдущие методы. Однако в этом случае он возвращает только два файла: один для стандартного ввода, а другой – для стандартного вывода и стандартного вывода.
Этот метод доступен для платформ Unix и Windows также устарел, начиная с версии 2.6. Чтобы заменить его соответствующим вызовом Popen подпроцесса, сделайте следующее:
| Методика | Заменяется |
|---|---|
| (child_stdin, child_stdout_and_stderr) = os.popen4 (‘cmd’, mode, bufsize) | p = Popen (‘cmd’, shell = True, bufsize = bufsize, stdin = PIPE, stdout = PIPE, stderr = STDOUT, close_fds = True) (child_stdin, child_stdout_and_stderr) = (p.stdin, p.stdout) |
Следующий код даст тот же результат, что и в предыдущих примерах, который показан в первом выводе кода выше.
import os
in, out = os.popen4('ls -la')
print(we.read())
Как видно из приведенного выше кода, метод очень похож на popen2. Однако выходной файл в программе покажет объединенные результаты потоков stdout и stderr.
Отличия
Все различия между командами popen * связаны с их выводом, который кратко изложен в таблице ниже:
| Методика | Аргументы |
|---|---|
| popen1 | stdin |
| popen2 | stdin, stdout |
| popen3 | stdin, stdout и stderr |
| popen4 | stdin, stdout и stderr |
Кроме того, popen2, popen3 и popen4 доступны только в Python 2, но не в Python 3. В Python 3 доступен метод popen, но вместо него рекомендуется использовать модуль subprocess.
Метод susbprocess
Модуль subprocess был создан с намерением заменить несколько методов, доступных в модуле os, которые не считались очень эффективными. В этом модуле мы находим новый класс Popen.
Документация Python рекомендует использовать Popen в сложных случаях, когда другие методы, такие как subprocess.call, не могут удовлетворить наши потребности. Этот метод позволяет выполнять программу как дочерний процесс. Поскольку это выполняется операционной системой, как отдельный процесс, результаты зависят от платформы.
Доступны следующие параметры:
subprocess.Popen(args, bufsize=0, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=False, shell=False, cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0)
Одно из основных отличий Popen заключается в том, что это класс, а не просто метод. Таким образом, когда мы вызываем subprocess.Popen, мы фактически вызываем конструктор класса Popen.
В конструкторе довольно много аргументов. Самым важным для понимания является args, который содержит команду для процесса, который мы хотим запустить. Его можно указать, как последовательность параметров (через массив) или как одну командную строку.
Второй аргумент, который важно понять, – это оболочка, значение которой по умолчанию равно False. В Unix, когда нам нужно запустить команду, принадлежащую оболочке, например ls -la, нам нужно установить shell = True.
Например, следующий код вызовет команду Unix ls -la через оболочку.
import subprocess
subprocess.Popen('ls -la', shell=True)
Результаты можно увидеть в выводе ниже:
$ python subprocess_popen_test.py total 40 drwxr-xr-x 7 scott staff 238 Nov 9 09:13 . drwxr-xr-x 29 scott staff 986 Nov 9 09:08 .. -rw-r--r-- 1 scott staff 52 Nov 9 09:13 popen2_test.py -rw-r--r-- 1 scott staff 55 Nov 9 09:14 popen3_test.py -rw-r--r-- 1 scott staff 53 Nov 9 09:14 popen4_test.py -rw-r--r-- 1 scott staff 49 Nov 9 09:13 popen_test.py -rw-r--r-- 1 scott staff 56 Nov 9 09:16 subprocess_popen_test.py
Используя следующий пример на компьютере с Windows, мы можем легче увидеть различия в использовании параметра оболочки. Здесь мы открываем Microsoft Excel из оболочки или как исполняемую программу. Из оболочки это похоже на то, как если бы мы открывали Excel из командного окна.
Следующий код откроет Excel из оболочки (обратите внимание, что мы должны указать shell = True):
import subprocess
subprocess.Popen("start excel", shell=True)
Однако мы можем получить те же результаты, вызвав исполняемый файл Excel. В этом случае мы не используем оболочку, поэтому оставляем ее со значением по умолчанию (False), но мы должны указать полный путь к исполняемому файлу.
import subprocess
subprocess.Popen("C:\Program Files (x86)\Microsoft Office\Office15\excel.exe")
Кроме того, когда мы создаем экземпляр класса Popen, у нас есть доступ к нескольким полезным методам:
| Методика | Описание |
|---|---|
| Popen.poll() | Проверяет, завершен ли дочерний процесс. |
| Popen.wait() | Подождите, пока дочерний процесс завершится. |
| Popen.communicate() | Позволяет взаимодействовать с процессом. |
| Popen.send_signal() | Посылает сигнал дочернему процессу. |
| Popen.terminate() | Останавливает дочерний процесс. |
| Popen.kill() | Убивает дочерний процесс. |
Полный список можно найти в документации подпроцесса. Наиболее часто используемый метод – это общение.
Метод связи позволяет нам считывать данные со стандартного ввода, а также позволяет отправлять данные на стандартный вывод. Он возвращает кортеж, определенный как (stdoutdata, stderrdata).
Например, следующий код объединяет команды Windows dir и sort.
import subprocess
p1 = subprocess.Popen('dir', shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
p2 = subprocess.Popen('sort /R', shell=True, stdin=p1.stdout)
p1.stdout.close()
out, err = p2.communicate()
Чтобы объединить обе команды, мы создаем два подпроцесса: один для команды dir, а другой – для команды sort. Поскольку мы хотим выполнить сортировку в обратном порядке, мы добавляем параметр /R к вызову сортировки.
Мы определяем стандартный вывод процесса 1 как PIPE, что позволяет нам использовать вывод процесса 1 в качестве ввода для процесса 2. Затем нам нужно закрыть стандартный вывод процесса 1, чтобы его можно было использовать как ввод для процесса 2. Связь между процессами достигается с помощью метода связи.
Запуск этого из командной оболочки Windows дает следующее:
> python subprocess_pipe_test.py
11/09/2017 08:52 PM 234 subprocess_pipe_test.py
11/09/2017 07:13 PM 99 subprocess_pipe_test2.py
11/09/2017 07:08 PM 66 subprocess_pipe_test3.py
11/09/2017 07:01 PM 56 subprocess_pipe_test4.py
11/09/2017 06:48 PM <DIR> ..
11/09/2017 06:48 PM <DIR> .
Volume Serial Number is 2E4E-56A3
Volume in drive D is ECA
Directory of D:\MyPopen
4 File(s) 455 bytes
2 Dir(s) 18,634,326,016 bytes free
Заключение
В прошлом методы ОС представляли собой хороший вариант, однако в настоящее время в модуле подпроцесса есть несколько методов, которые являются более мощными и эффективными в использовании. Среди доступных инструментов есть класс Popen, который можно использовать в более сложных случаях. Этот класс также содержит метод связи, который помогает нам объединять различные команды для более сложных функций.