Язык Python примерно в 30 раз медленнее языка С. Но процесс разработки на Python примерно в 10 раз быстрее. В этой статье приведён код примеров передачи и приёма различных типов данных из Python в С и обратно, чтобы сочетать преимущества двух языков.
Сразу могу сказать по собственному опыту, что использование языка С через Python категорически востребованно в области анализа больших данных.
Бывают случаи, когда использование чистого Python делает задачу полностью невыполнимой. Например, стоит использовать язык С, если нужно сделать "молотилку", которая миллион раз проходит один и тот же огромный массив, а подобный цикл на Python может выполняться минуты, часы или даже дни.
Для передачи данных между Python и С можно использовать модуль CTypes. При этом есть несколько проблем.
- Во-первых, в Python приходится вручную освобождать память после использования переменных, созданных в С через динамическое выделение памяти malloc, calloc и т.п. (в С нет встроенного сборщика мусора). Конечно, можно понадеяться на автоматическое освобождение памяти самой операционной системой после завершения скрипта на С, когда библиотека выгружается из Python. Но для этого надо вручную выгрузить её из Python и надеяться, что С не вернёт ошибку "Segmentation fault". То есть надо после clib = ctypes.CDLL() сделать del clib или поместить вообще весь код с использованием ctypes.CDLL внутрь функции или условия if, чтобы clib сама удалилась из-за области видимости. При этом ctypes не умеет работать с менеджером контекста with.
- Вторая проблема заключается в том, что Python хорошо работает с переменными типа double и наровит не только передать их в С, но и ожидает получить от С тоже переменные типа double. Поэтому надо заранее указывать типы передаваемых и получаемых переменных и предварительно конвертировать их через numpy.
- Третий нюанс. Передавать массивы надо через указатели. Опять же numpy в помощь.
Далее приведён программный код двух файлов проекта, в котором передаются и принимаются (между Python и C) различные типы данных (переменные, массивы и даже структуры):
- example.c - исходный код на языке С, содержит методы, которые будут исполняться по с данными, переданными из Python, возвращая результат обратно в Python.
- script.py - скрипт на Python для передачи и приёма различных типов данных из Python в С и обратно.
Для компиляции файла "example.c" надо перейти в папку с файлом и выполнить команду:
gcc -shared -fPIC -O2 example.c -o example.so
example.c
#include <stdio.h> #include <stdlib.h> void free_mem_int_ptr(int *ptr) { // Освобождает память по указателю if (ptr != NULL) { free(ptr); ptr = NULL; } } void free_mem_double_ptr(double *ptr) { // Освобождает память по указателю if (ptr != NULL) { free(ptr); ptr = NULL; } } void free_mem_int_ptr2ptr(int **ptr, int size) { // Освобождает память по двойному указателю // size - количество внутренних указателей if (ptr != NULL) { for (int i = 0; i < size; i++) { free(ptr[i]); ptr[i] = NULL; } free(ptr); ptr = NULL; } } // === Числа int === int func_int(int src_int) { // Изменение и возврат int printf("C. Получено значение int: %d\n", src_int); src_int = 42; printf("C. Значение int изменено на: %d\n", src_int); return src_int; } // === Числа double === double func_double(double src_double) { // Изменение и возврат double printf("C. Получено значение double: %f\n", src_double); src_double = 42.42; printf("C. Значение double изменено на: %f\n", src_double); return src_double; } // === Массив int === typedef struct { // Структура данных для возврата int *arr; int size; } ReturnIntArrayStruct; void free_mem_int_array(int *arr) { // Функция освобождения памяти от структуры с результатом if (arr != NULL) { // удаляем только то, что было размечено через malloc free_mem_int_ptr(arr); } } ReturnIntArrayStruct func_int_array(int *src_int_array, int src_int_array_size) { // Печать содержимого массива printf("C. Получен массив int {"); for (int i = 0; i < src_int_array_size; i++) { printf("%d ", src_int_array[i]); } printf("}\n"); // Создание нового массива int a_size = 5; int *a = (int *)malloc(a_size * sizeof(int)); for (int i = 0; i < a_size; i++) { a[i] = i+1; } // Печать содержимого массива printf("C. Массив изменён на: {"); for (int i = 0; i < a_size; i++) { printf("%d ", a[i]); } printf("}\n"); // Возврат структуры, описывающей массив ReturnIntArrayStruct res; res.size = a_size; res.arr = a; return res; } // === Массив double === typedef struct { // Структура данных для возврата double *arr; int size; } ReturnDoubleArrayStruct; void free_mem_double_array(double *arr) { // Функция освобождения памяти от структуры с результатом if (arr != NULL) { // удаляем только то, что было размечено через malloc free_mem_double_ptr(arr); } } ReturnDoubleArrayStruct func_double_array(double *src_double_array, int src_double_array_size) { // Печать содержимого массива printf("C. Получен массив double: {"); for (int i = 0; i < src_double_array_size; i++) { printf("%.1f ", src_double_array[i]); } printf("}\n"); // Создание нового массива int a_size = 3; double *a = (double *)malloc(a_size * sizeof(double)); for (int i = 0; i < a_size; i++) { a[i] = i+1; } // Печать содержимого массива printf("C. Массив изменён на: {"); for (int i = 0; i < a_size; i++) { printf("%.1f ", a[i]); } printf("}\n"); // Возврат структуры, описывающей массив ReturnDoubleArrayStruct res; res.size = a_size; res.arr = a; return res; } // === Двумерный массив int === typedef struct { // Структура данных для возврата int **arr; int size_1; int size_2; } ReturnIntArray2DStruct; void free_mem_int_array2D(int **arr, int size) { // Функция освобождения памяти от структуры с результатом // size - внешний размер двумерного массива if (arr != NULL) { // удаляем только то, что было размечено через malloc free_mem_int_ptr2ptr(arr, size); } } ReturnIntArray2DStruct func_int_array2D(int **src_int_array2D, int src_int_array2D_size_1, int src_int_array2D_size_2) { // Печать содержимого массива printf("C. Получен двумерный массив int: {"); for (int i = 0; i < src_int_array2D_size_1; i++) { printf("{"); for (int j = 0; j < src_int_array2D_size_2; j++) { printf("%d ", src_int_array2D[i][j]); } printf("}, "); } printf("}\n"); // Создание нового массива int a_size_1 = 3; int a_size_2 = 2; int **a = malloc(a_size_1 * sizeof(int *)); int k = 10; for (int i = 0; i < a_size_1; i++) { a[i] = malloc(a_size_2 * sizeof(int)); for (int j = 0; j < a_size_2; j++) { a[i][j] = k++; } } // Печать содержимого массива printf("C. Двумерный массив изменён на: {"); for (int i = 0; i < a_size_1; i++) { printf("{"); for (int j = 0; j < a_size_2; j++) { printf("%d ", a[i][j]); } printf("}, "); } printf("}\n"); // Возврат структуры, описывающей массив ReturnIntArray2DStruct res; res.size_1 = a_size_1; res.size_2 = a_size_2; res.arr = a; return res; }
script.py
import os import ctypes import numpy as np def func_int(clib): # Число int для передачи в функцию a = 5 print('Python. Отправляем число int:', a) # Готовим данные для передачи src_int = ctypes.c_int(a) # Определение функции и типов данных func = clib.func_int func.argtypes = [ctypes.c_int] func.restype = ctypes.c_int # Выполняем функцию result_int = func(src_int) # Проверка результата print('Python. Получено значение:', result_int) def func_double(clib): # Число double для передачи в функцию a = 5.5 print('Python. Отправляем число double:', a) # Готовим данные для передачи src_double = ctypes.c_double(a) # Определение функции и типов данных func = clib.func_double func.argtypes = [ctypes.c_double] func.restype = ctypes.c_double # Выполняем функцию и вернём результатч result_double = func(src_double) # Проверка результата print('Python. Получено значение:', result_double) def func_int_array(clib): # Массив int для передачи в функцию a = [-5, -6, -7] print('Python. Отправляем массив int:', a) src_int_array = np.int32(a).ctypes\ .data_as(ctypes.POINTER(ctypes.c_int)) src_int_array_size = len(a) # Определение структуры результата class ResultStruct(ctypes.Structure): _fields_ = [ # Массив (указатель) ("arr", ctypes.POINTER(ctypes.c_int)), # Размер массива ("size", ctypes.c_int) ] # Определение функции и типов данных func = clib.func_int_array func.argtypes = [ctypes.POINTER(ctypes.c_int), ctypes.c_int] func.restype = ResultStruct # Выполняем функцию res = func(src_int_array, src_int_array_size) # Проверка результата res_arr = res.arr[:res.size] print('Python. Получен массив:', res_arr) # Освобождаем память у переменных-указателей # и всего, что было размечено через malloc fc = clib.free_mem_int_array fc(res.arr) # Теперь можно пользоваться массивом. # res_arr def func_double_array(clib): # Массив double для передачи в функцию a = [-1.2, -3.4, -5.6] print('Python. Отправляем массив double:', a) src_double_array = np.double(a).ctypes\ .data_as(ctypes.POINTER(ctypes.c_double)) src_double_array_size = len(a) # Определение структуры результата class ResultStruct(ctypes.Structure): _fields_ = [ # Массив (указатель) ("arr", ctypes.POINTER(ctypes.c_double)), # Размер массива ("size", ctypes.c_int) ] # Определение функции и типов данных func = clib.func_double_array func.argtypes = [ctypes.POINTER(ctypes.c_double), ctypes.c_int] func.restype = ResultStruct # Выполняем функцию res = func(src_double_array, src_double_array_size) # Проверка результата res_arr = res.arr[:res.size] print('Python. Получен массив:', res_arr) # Освобождаем память у переменных-указателей # и всего, что было размечено через malloc fc = clib.free_mem_double_array fc(res.arr) # Теперь можно пользоваться массивом. # res_arr def func_int_array2D(clib): # Двумерный массив int для передачи в функцию a = [[1,2,3], [4,5,6]] print('Python. Отправляем двумерный массив:', a) x = (ctypes.POINTER(ctypes.c_int) * len(a))() for i, row in enumerate(a): x[i] = (ctypes.c_int * len(row))(*row) src_int_array2D = x src_int_array2D_size_1 = len(a) src_int_array2D_size_2 = len(a[0]) # Определение структуры результата class ResultStruct(ctypes.Structure): _fields_ = [ # Массив (указатель) ("arr", ctypes.POINTER(ctypes.POINTER(ctypes.c_int))), # Размер массива ("size_1", ctypes.c_int), # Размер массива ("size_2", ctypes.c_int) ] # Определение функции и типов данных func = clib.func_int_array2D func.argtypes = [ctypes.POINTER(ctypes.POINTER(ctypes.c_int)), ctypes.c_int, ctypes.c_int] func.restype = ResultStruct # Выполняем функцию res = func(src_int_array2D, src_int_array2D_size_1, src_int_array2D_size_2) # Проверка результата res_arr = [[res.arr[i][j] for j in range(res.size_2)] for i in range(res.size_1)] print('Python. Получен двумерный массив:', res_arr) # Освобождаем память у переменных-указателей # и всего, что было размечено через malloc fc = clib.free_mem_int_array2D fc(res.arr, res.size_1) # Теперь можно пользоваться массивом. # res_arr # Загружаем общую библиотеку clib = ctypes.CDLL(os.path.join(os.getcwd(), 'example.so')) print('Передаём различные типы данных в С ' 'и получаем ответы с теми же типами:') print('\nЧисла int') func_int(clib) print('\nЧисла double') func_double(clib) print('\nМассив int') func_int_array(clib) print('\nМассив double') func_double_array(clib) print('\nДвумерный массив int') func_int_array2D(clib)
При выполнениии script.py результат должен быть таким:
Передаём различные типы данных в С и получаем ответы с теми же типами: Числа int Python. Отправляем число int: 5 C. Получено значение int: 5 C. Значение int изменено на: 42 Python. Получено значение: 42 Числа double Python. Отправляем число double: 5.5 C. Получено значение double: 5.500000 C. Значение double изменено на: 42.420000 Python. Получено значение: 42.42 Массив int Python. Отправляем массив int: [-5, -6, -7] C. Получен массив int {-5 -6 -7 } C. Массив изменён на: {1 2 3 4 5 } Python. Получен массив: [1, 2, 3, 4, 5] Массив double Python. Отправляем массив double: [-1.2, -3.4, -5.6] C. Получен массив double: {-1.2 -3.4 -5.6 } C. Массив изменён на: {1.0 2.0 3.0 } Python. Получен массив: [1.0, 2.0, 3.0] Двумерный массив int Python. Отправляем двумерный массив: [[1, 2, 3], [4, 5, 6]] C. Получен двумерный массив int: {{1 2 3 }, {4 5 6 }, } C. Двумерный массив изменён на: {{10 11 }, {12 13 }, {14 15 }, } Python. Получен двумерный массив: [[10, 11], [12, 13], [14, 15]]
P.S.
Пожалуйста, дайте знать через комментарии, если найдёте ошибку.