Взаимодействие с внешними приложениями#

Исполнение программ#

Запуск с ожиданием завершения#

Простейший метод запуска программы метод os.system:

import os

ret = os.system("dir")
# ret - код завершения программы
# 0 - успешно, иначе ошибка
basics1.ipynb  calc2.ipynb  data2.ipynb  _images    locale     plot.ipynb
basics2.ipynb  conf.py	    ext.ipynb	 index.rst  mt.ipynb   _static
calc1.ipynb    data1.ipynb  gph.ipynb	 intro.md   net.ipynb  _templates

Вызов блокирующий - программа ждет завершения команды вызванной через os.system.

Запуск с получением вывода#

import subprocess

output = subprocess.check_output('ping 127.0.0.1 -n 6', encoding="cp866")

print(output)
---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
Cell In[2], line 3
      1 import subprocess
----> 3 output = subprocess.check_output('ping 127.0.0.1 -n 6', encoding="cp866")
      5 print(output)

File /usr/lib/python3.12/subprocess.py:466, in check_output(timeout, *popenargs, **kwargs)
    463         empty = b''
    464     kwargs['input'] = empty
--> 466 return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
    467            **kwargs).stdout

File /usr/lib/python3.12/subprocess.py:548, in run(input, capture_output, timeout, check, *popenargs, **kwargs)
    545     kwargs['stdout'] = PIPE
    546     kwargs['stderr'] = PIPE
--> 548 with Popen(*popenargs, **kwargs) as process:
    549     try:
    550         stdout, stderr = process.communicate(input, timeout=timeout)

File /usr/lib/python3.12/subprocess.py:1026, in Popen.__init__(self, args, bufsize, executable, stdin, stdout, stderr, preexec_fn, close_fds, shell, cwd, env, universal_newlines, startupinfo, creationflags, restore_signals, start_new_session, pass_fds, user, group, extra_groups, encoding, errors, text, umask, pipesize, process_group)
   1022         if self.text_mode:
   1023             self.stderr = io.TextIOWrapper(self.stderr,
   1024                     encoding=encoding, errors=errors)
-> 1026     self._execute_child(args, executable, preexec_fn, close_fds,
   1027                         pass_fds, cwd, env,
   1028                         startupinfo, creationflags, shell,
   1029                         p2cread, p2cwrite,
   1030                         c2pread, c2pwrite,
   1031                         errread, errwrite,
   1032                         restore_signals,
   1033                         gid, gids, uid, umask,
   1034                         start_new_session, process_group)
   1035 except:
   1036     # Cleanup if the child failed starting.
   1037     for f in filter(None, (self.stdin, self.stdout, self.stderr)):

File /usr/lib/python3.12/subprocess.py:1955, in Popen._execute_child(self, args, executable, preexec_fn, close_fds, pass_fds, cwd, env, startupinfo, creationflags, shell, p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite, restore_signals, gid, gids, uid, umask, start_new_session, process_group)
   1953     err_msg = os.strerror(errno_num)
   1954 if err_filename is not None:
-> 1955     raise child_exception_type(errno_num, err_msg, err_filename)
   1956 else:
   1957     raise child_exception_type(errno_num, err_msg)

FileNotFoundError: [Errno 2] No such file or directory: 'ping 127.0.0.1 -n 6'

Вызов блокирующий - программа ждет завершения команды вызванной через subprocess.check_output.

Запуск без ожидания завершения#

Метод subprocess.Popen по умолчанию запускает процесс без ожидания завершения:

import subprocess

q = subprocess.Popen("ping 127.0.0.1 -n 6", shell=True)

while q.poll() is None:
    print('waiting...')
    sleep(1)
 
print("Finished with code", q.poll())

Что еще нужно знать о запуске внешних программ#

  • Если вы нажимаете Ctrl-C пока внешняя программа запущена через os.system, то этот сигнал уйдет ей, а не Python.

  • Можно взаимодействовать с интерактивными консольными приложениями пока те запущены через Popen. В частности их можно принудительно завершить или прочитать вывод до завершения самой программы.

  • Большинство приложений с графическим интерфейсом в качестве первого и единственного аргумента принимают имя файла, который должен быть в них открыт.

Загрузка С библиотек#

Python может загружать библиотеки на С и вызывать функции из них. Для этого можно воспользоваться модулем стандартной библиотеки ctypes или модулем cffi.

Модуль ctypes требует явного описания сигнатуры (списка аргументов и возвращаемого значения) вызываемой функции на Python, cffi, же включает синтаксический анализатор C, благодаря которому может разбирать заголовочные файлы .h и получать оттуда информацию о структурах.

Загрузка библиотеки в ctypes#

import ctypes

# В *nix разделяемые библиотеки - файлы .so
lib = ctypes.CDLL('./mylib.so')

# В Windows - файлы .dll
lib = ctypes.CDLL('./mylib.dll')

Вызов функции в ctypes#

foo = lib.foo
foo.restype = ctypes.c_double
foo.argtypes = [ctypes.c_int, ctypes.c_double]

# Теперь функцию foo можно вызывать, как обычную функцию Python.

ret = foo(5, 3.2)
print(ret)

Некоторые популярные типы ctypes#

Тип ctypes

Тип C

Тип Python

c_byte

char

int

c_ubyte

unsigned char

int

c_int

int

int

c_double

double

float

c_char_p

char *

str или None

c_int * 10

int [10]

List[int]

Так как строки в Python являются константами, для создания изменяемого массива типа c_char_p используется метод create_string_buffer. Подробнее в документации.

Загрузка библиотеки и вызов функции в cffi#

import cffi

# Создаем объект пространства имен cffi
ffi = cffi.FFI()

# Загружаем заголовочный файл
with open("mylib_header.h") as f:
    ffi.cdef(f.read())

# Загружаем библиотеку
lib = ffi.dlopen("mylib.so")

# Вызываем функцию
result = lib.foo(5, 3.2)

# Создаем объект - структуру данных объявленную в заголовочном файле
my_data_struct = self.ffi.new("DataStruct *")

my_data_struct.a = 15 # Работаем с полями структуры на Python

ffi.buffer(my_data_struct) # Получаем bytes представление структуры

Что еще нужно знать о вызове С функций#

  • Написание критичных с точки зрения производительности участков кода на C — один из стандартных способов разогнать Python.

  • Ошибка в С библиотеке не может быть обработана на уровне Python и приведет к аварийному завершению интерпретатора.

  • Анализатор С pycparser который использует cffi поддерживает не все возможности языка С, а также не умеет обрабатывать директивы препроцессора (#include, #ifdef и т.п.).

  • Для CPython можно писать модули С — CPython Extensions.