- PVSM.RU - https://www.pvsm.ru -
Перевод статьи подготовлен специально для студентов курса «Разработчик Python» [1].
Внимание: код в этой статье лицензирован под GNU AGPLv3 [2].
Я написал это руководство, поскольку не смог найти такого, которое будет объединять в себе все полезное о ctypes. Надеюсь, эта статья сделает чью-то жизнь намного легче.
Содержание:
Перед переписыванием исходного кода Python на С, рассмотрим базовые методы оптимизации в Python.
Встроенные структуры данных в Python, такие как set и dict, написаны на С. Они работают гораздо быстрее, чем ваши собственные структуры данных, написанные как классы Python. Другие структуры данных, помимо стандартных set, dict, list и tuple описаны в документации модуля collections [3].
Вместо того, чтобы добавлять элементы к списку стандартным методом, воспользуйтесь списочными выражениями.
# Slow
mapped = []
for value in originallist:
mapped.append(myfunc(value))
# Faster
mapped = [myfunc(value) in originallist]
Модуль ctypes [4] позволяет вам взаимодействовать с кодом на С из Python без использования модуля subprocess или другого подобного модуля для запуска других процессов из CLI.
Тут всего две части: компиляция кода на С для загрузки в качестве shared object и настройка структур данных в коде на Python для сопоставления их с типами С.
В этой статье я буду соединять свой код на Python с lcs.c [5], которая находит самую длинную подпоследовательность в двух списках строк. Я хочу, чтобы в Python работало следующее:
list1 = ['My', 'name', 'is', 'Sam', 'Stevens', '!']
list2 = ['My', 'name', 'is', 'Alex', 'Stevens', '.']
common = lcs(list1, list2)
print(common)
# ['My', 'name', 'is', 'Stevens']
Одна из проблем заключается в том, что эта конкретная функция на С — это сигнатура функции, которая принимает списки строк в качестве типов аргументов, и возвращает тип, у которого нет фиксированной длины. Я решаю эту задачу с помощью структуры последовательности, содержащей указатели и длину.
Сначала, исходный код на С (lcs.c) компилируется в lcs.so, чтобы загружаться в Python.
gcc -c -Wall -Werror -fpic -O3 lcs.c -o lcs.o
gcc -shared -o lcs.so lcs.o
А теперь мы начнем писать код на Python, используя полученный файл shared object.
Ниже представлены две структуры данных, которые используются в моем коде на С.
struct Sequence
{
char **items;
int length;
};
struct Cell
{
int index;
int length;
struct Cell *prev;
};
А вот перевод этих структур на Python.
import ctypes
class SEQUENCE(ctypes.Structure):
_fields_ = [('items', ctypes.POINTER(ctypes.c_char_p)),
('length', ctypes.c_int)]
class CELL(ctypes.Structure):
pass
CELL._fields_ = [('index', ctypes.c_int), ('length', ctypes.c_int),
('prev', ctypes.POINTER(CELL))]
Несколько заметок:
ctypes.Structure._fields_ представляет из себя список кортежей. Каждый кортеж это (<variable-name>, <ctypes.TYPE>).ctypes есть похожие типы c_char (char) и c_char_p (*char).ctypes также есть POINTER(), который создает указатель типа из каждого типа ему переданного.CELL, вы должны передать начальное объявление, а затем добавить поля _fields_, чтобы позже получить ссылку на себя же.CELL в своем коде на Python, мне не нужно было писать эту структуру, но у нее есть интересное свойство в рекурсивном поле. Кроме того, мне нужен был какой-нибудь код для преобразования типов Python в новые структуры на С. Теперь вы можете использовать свою новую функцию на С, чтобы ускорить работу кода на Python.
def list_to_SEQUENCE(strlist: List[str]) -> SEQUENCE:
bytelist = [bytes(s, 'utf-8') for s in strlist]
arr = (ctypes.c_char_p * len(bytelist))()
arr[:] = bytelist
return SEQUENCE(arr, len(bytelist))
def lcs(s1: List[str], s2: List[str]) -> List[str]:
seq1 = list_to_SEQUENCE(s1)
seq2 = list_to_SEQUENCE(s2)
# struct Sequence *lcs(struct Sequence *s1, struct Sequence *s2)
common = lcsmodule.lcs(ctypes.byref(seq1), ctypes.byref(seq2))[0]
ret = []
for i in range(common.length):
ret.append(common.items[i].decode('utf-8'))
lcsmodule.freeSequence(common)
return ret
lcsmodule = ctypes.cdll.LoadLibrary('lcsmodule/lcs.so')
lcsmodule.lcs.restype = ctypes.POINTER(SEQUENCE)
list1 = ['My', 'name', 'is', 'Sam', 'Stevens', '!']
list2 = ['My', 'name', 'is', 'Alex', 'Stevens', '.']
common = lcs(list1, list2)
print(common)
# ['My', 'name', 'is', 'Stevens']
Немного заметок:
**char (список строк) проводит сопоставление напрямую в список байт в Python.lcs.c есть функция lcs() с сигнатурой struct Sequence *lcs(struct Sequence *s1, struct Sequence *s2). Чтобы настроить возвращаемый тип, я использую lcsmodule.lcs.restype = ctypes.POINTER(SEQUENCE). ctypes.byref(), который возвращает «легкий указатель» на ваш объект (быстрее, чем ctypes.POINTER()).common.items – это список байт, они могут быть декодированы для получения ret в виде списка str.Оптимизированный код на Python – это код, который вы написали на С и обернули в Python.
Внимание: Сам я никогда PyPy не пользовался.
Одна из самых простых оптимизаций заключается в запуске ваших программ в среде выполнения PyPy [6], которая содержит в себе JIT-компилятор (just-in-time), который ускоряет работу циклов, компилируя их в машинный код при многократном выполнении.
Если у вас появятся комментарии или вы захотите что-то обсудить, напишите мне (samuel.robert.stevens@gmail.com).
На этом все. До встречи на курсе [1]!
Автор: MaxRokatansky
Источник [7]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/python/347952
Ссылки в тексте:
[1] «Разработчик Python»: https://otus.pw/KTix/
[2] GNU AGPLv3: https://samuelstevens.me/license-gnu
[3] collections: https://docs.python.org/dev/library/collections.html#module-collections
[4] ctypes: https://docs.python.org/3/library/ctypes.html
[5] lcs.c: https://samuelstevens.me/lcs-source
[6] PyPy: https://www.pypy.org/
[7] Источник: https://habr.com/ru/post/490244/?utm_campaign=490244&utm_source=habrahabr&utm_medium=rss
Нажмите здесь для печати.