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

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

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

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

import os

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

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

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

import subprocess

output = subprocess.check_output(['ping', '127.0.0.1', '-c', '3'])

print(output.decode())
PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.044 ms
64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.042 ms
64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.043 ms

--- 127.0.0.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2058ms
rtt min/avg/max/mdev = 0.042/0.043/0.044/0.000 ms

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

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

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

import subprocess
from time import sleep

q = subprocess.Popen("ping 127.0.0.1 -c 3", shell=True)

while q.poll() is None:
    print('waiting...')
    sleep(1)
 
print("Finished with code", q.poll())
waiting...
PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.037 ms
waiting...
64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.041 ms
waiting...
64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.048 ms

--- 127.0.0.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2043ms
rtt min/avg/max/mdev = 0.037/0.042/0.048/0.004 ms
Finished with code 0

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

  • Если вы нажимаете 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.