# Python C расширения

Интересной возможностью, которую предлагает разработчикам CPython, является простота использования C-кода в Python.

Существует три метода, с помощью которых разработчик может вызвать C функцию из Python кода - `ctypes`, `SWIG` и `Python/C API`. У каждого метода есть свои преимущества и недостатки.

Для начала, зачем нам вообще это может потребоваться?

Несколько популярных причин:

* Вам нужна скорость и вы знаете, что C в 50х раз быстрее Python
* Вам нужна конкретная C-библиотека и вы не хотите писать "велосипед" на Python
* Вам нужен низкоуровневый интерфейс управления ресурсами для работы с памятью

  и файлами
* Просто потому что Вам так хочется

## CTypes

Модуль [ctypes](https://docs.python.org/2/library/ctypes.html) один из самых простых способов вызывать C-функции из Python. Он предоставляет C-совместимые типы данных и функции для загрузки DLL, что позволяет обращаться к библиотекам C без их модификации. Отсутствие необходимости изменять C-код объясняет простоту данного метода.

**Пример**

Простой C-код для суммирования двух чисел, сохраните его как `add.c`

```c
// Простой C-файл - суммируем целые и действительные числа

#include <stdio.h>

int add_int(int, int);
float add_float(float, float);

int add_int(int num1, int num2){
    return num1 + num2;
}

float add_float(float num1, float num2){
    return num1 + num2;
}
```

Теперь скомпилируем C-файл в `.so`-файл (DLL под Windows). Так мы получим файл `adder.so`.

```bash
# Для Linux
$  gcc -shared -Wl,-soname,adder -o adder.so -fPIC add.c

# Для Mac
$ gcc -shared -Wl,-install_name,adder.so -o adder.so -fPIC add.c
```

Теперь Python-код:

```python
from ctypes import *

# Загружаем библиотеку
adder = CDLL('./adder.so')

# Находим сумму целых чисел
res_int = adder.add_int(4,5)
print("Сумма 4 и 5 = " + str(res_int))

# Находим сумму действительных чисел
a = c_float(5.5)
b = c_float(4.1)

add_float = adder.add_float
add_float.restype = c_float
print("Сумма 5.5 и 4.1 = " + str(add_float(a, b)))
```

Результат:

```
Сумма 4 и 5 = 9
Сумма 5.5 и 4.1 =  9.60000038147
```

В примере выше, C-файл содержит простой код - две функции: одна для нахождения суммы двух целых чисел, другая - действительных.

В Python-коде мы сначала импортируем модуль `ctypes`. Затем функция `CDLL` из того же модуля используется для загрузки C-библиотеки. Теперь функции из C-кода доступны для нас через переменную `adder`. Когда мы вызываем `adder.add_int()`, то автоматически вызывается C-функция `add_int`. Интерфейс модуля `ctypes` позволяет использовать питоновские целые числа и строки при вызове C-функций.

Для других типов, например логического или действительных чисел, мы должны использовать корректные `ctypes`. Мы делаем это при передаче параметров в `adder.add_float()`. Сначала мы создаём требуемый тип `c_float` из `float`, затем используем в качестве аргумента для C-кода. Этот метод простой и аккуратный, но ограниченный. Мы, к примеру, не можем оперировать объектами на стороне C-кода.

## SWIG

Simplified Wrapper and Interface Generator, или SWIG для краткости, это другой способ работы с C-кодом из Python. В этом методе разработчик должен написать отдельный файл, описывающий интерфейс, который будет передаваться в SWIG (утилиту командной строки).

Python-разработчики обычно не используют данный подход, поскольку в большинстве случаев он неоправданно сложен. Тем не менее, это отличный вариант, когда у вас есть C/C++ код, к которому нужно обращаться из множества различных языков.

**Пример** (с [сайта SWIG](http://www.swig.org/tutorial.html))

C-код, `example.c` содержит различные функции и переменные:

```c
#include <time.h>
double My_variable = 3.0;

int fact(int n) {
    if (n <= 1) return 1;
    else return n*fact(n-1);
}

int my_mod(int x, int y) {
    return (x%y);
}

char *get_time()
{
    time_t ltime;
    time(&ltime);
    return ctime(&ltime);
}
```

Файл, описывающий интерфейс. Он не будет изменяться в зависимости от языка, на который вы хотите портировать свой C-код:

```
/* example.i */
%module example
%{
/* Помещаем сюда заголовочные файлы или объявления функций */
extern double My_variable;
extern int fact(int n);
extern int my_mod(int x, int y);
extern char *get_time();
%}

extern double My_variable;
extern int fact(int n);
extern int my_mod(int x, int y);
extern char *get_time();
```

Компиляция:

```
unix % swig -python example.i
unix % gcc -c example.c example_wrap.c \
    -I/usr/local/include/python2.1
unix % ld -shared example.o example_wrap.o -o _example.so
```

Python:

```python
>>> import example
>>> example.fact(5)
120
>>> example.my_mod(7,3)
1
>>> example.get_time()
'Sun Feb 11 23:01:07 1996'
>>>
```

Как мы можем видеть, SWIG позволяет добиваться нужного нам эффекта, но он требует дополнительных усилий, которые, однако, стоит затратить, если вас интересует возможность запуска C-кода из множества различных языков.

## Python/C API

[C/Python API](https://docs.python.org/2/c-api/) это, вероятно, наиболее широко применяемый метод - не благодаря своей простоте, а потому что он позволяет оперировать Python объектами из C кода.

Этот метод подразумевает написание C-кода специально для работы с Python. Все объекты Python представляются как структуры PyObject и заголовочный файл `Python.h` предоставляет различные функции для работы с объектами. Например, если `PyObject` одновременно `PyListType` (список), то мы можем использовать функцию `PyList_Size()`, чтобы получить длину списка. Это эквивалентно коду `len(some_list)` в Python. Большинство основных функций/операторов для стандартных Python объектов доступны в C через `Python.h`.

**Пример**

Давайте напишем С-библиотеку для суммирования всех элементов списка Python (все элементы являются числами).

Начнем с интерфейса, который мы хотим иметь в итоге. Вот Python-файл, использующий пока отсутствующую C-библиотеку:

```python
# Это не простой Python import, addList это C-библиотека
import addList

l = [1,2,3,4,5]
print("Сумма элементов списка - " + str(l) + " = " +  str(addList.add(l)))
```

Смотрится как обыкновенный Python-код, который импортирует и использует Python-модуль `addList`. Единственная разница - модуль `addList` написан на C.

Дальше на повестке у нас C-код, который будет встроен в Python-модуль `addList`, это может смотреться немного странно, однако, разобрав отдельные части, из которых состоит C-файл, вы увидите, что все относительно незатейливо.

*adder.c*

```c
// Python.h содержит все необходимые функции, для работы с объектами Python
#include <Python.h>

// Эту функцию мы вызываем из Python кода
static PyObject* addList_add(PyObject* self, PyObject* args){

  PyObject * listObj;

  // Входящие аргументы находятся в кортеже
  // В нашем случае есть только один аргумент - список, на который мы будем
  // ссылаться как listObj
  if (! PyArg_ParseTuple( args, "O", &listObj))
    return NULL;

  // Длина списка
  long length = PyList_Size(listObj);

  // Проходимся по всем элементам
  long i, sum =0;
  for(i = 0; i < length; i++){
    // Получаем элемент из списка - он также Python-объект
    PyObject* temp = PyList_GetItem(listObj, i);
    // Мы знаем, что элемент это целое число - приводим его к типу C long
    long elem = PyInt_AsLong(temp);
    sum += elem;
  }

  // Возвращаемое в Python-код значение также Python-объект
  // Приводим C long к Python integer
  return Py_BuildValue("i", sum);
}

// Немного документации для `add`
static char addList_docs[] =
    "add( ): add all elements of the list\n";

/*
Эта таблица содержит необходимую информацию о функциях модуля
<имя функции в модуле Python>, <фактическая функция>,
<ожидаемые типы аргументов функции>, <документация функции>
*/
static PyMethodDef addList_funcs[] = {
    {"add", (PyCFunction)addList_add, METH_VARARGS, addList_docs},
    {NULL, NULL, 0, NULL}
};

/*
addList имя модуля и это блок его инициализации.
<желаемое имя модуля>, <таблица информации>, <документация модуля>
*/
PyMODINIT_FUNC initaddList(void){
    Py_InitModule3("addList", addList_funcs,
                   "Add all ze lists");
}
```

Пошаговое объяснение:

* Заголовочный файл `<Python.h>` содержит все требуемые типы (для

  представления типов объектов в Python) и определения функций (для работы с

  Python-объектами).
* Дальше мы пишем функцию, которую собираемся вызывать из Python. По

  соглашению, имя функции принимается {module-name}\_{function-name}, которое

  в нашем случае - `addList_add`. Подробнее об этой функции будет дальше.
* Затем заполняем таблицу, которая содержит всю необходимую информацию о

  функциях, которые мы хотим иметь в модуле. Каждая строка относится к

  функции, последняя - контрольное значение (строка из null элементов).
* Затем идёт блок инициализации модуля - `PyMODINIT_FUNC init{module-name}`.

Функция `addList_add` принимает аргументы типа `PyObject` (args также является кортежем, но поскольку в Python всё является объектами, мы используем унифицированный тип `PyObject`). Мы парсим входные аргументы (фактически, разбиваем кортеж на отдельные элементы) при помощи `PyArg_ParseTuple()`. Первый параметр является аргументом для парсинга. Второй аргумент - строка, регламентирующая процесс парсинга элементов кортежа args. Знак на N-ой позиции строки сообщает нам тип N-ого элемента кортежа args, например - 'i' значит integer, 's' - строка и 'O' - Python-объект. Затем следует несколько аргументов, где мы хотели бы хранить выходные элементы `PyArg_ParseTuple()`. Число этих аргументов равно числу аргументов, которые планируется передавать в функцию модуля и их позиционность должна соблюдаться. Например, если мы ожидаем строку, целое число и список в таком порядке, сигнатура функции будет следующего вида:

```c
int n;
char *s;
PyObject* list;
PyArg_ParseTuple(args, "siO", &s, &n, &list);
```

В данном случае, нам нужно извлечь только объект списка и сохранить его в переменной `listObj`. Затем мы используем функцию `PyList_Size()` чтобы получить длину списка. Логика совпадает с `len(some_list)` в Python.

Теперь мы итерируем по списку, получая элементы при помощи функции `PyLint_GetItem(list, index)`. Так мы получаем PyObject\*. Однако, поскольку мы знаем, что Python-объекты еще и `PyIntType`, то используем функцию `PyInt_AsLong(PyObj *)` для получения значения. Выполняем процедуру для каждого элемента и получаем сумму.

Сумма преобразуется в Python-объект и возвращается в Python-код при помощи `Py_BuildValue()`. Аргумент "i" означает, что возвращаемое значение имеет тип integer.

В заключение мы собираем C-модуль. Сохраните следующий код как файл `setup.py`:

```python
# Собираем модули

from distutils.core import setup, Extension

setup(name='addList', version='1.0',\
      ext_modules=[Extension('addList', ['adder.c'])])
```

и запустите:

```bash
python setup.py install
```

Это соберёт и установит C-файл в Python-модуль, который нам требуется.

Теперь осталось только протестировать работоспособность:

```python
# Модуль, вызывающий C-код
import addList

l = [1,2,3,4,5]
print("Сумма элементов списка - " + str(l) + " = " +  str(addList.add(l)))
```

Результат:

```
Сумма элементов списка - [1, 2, 3, 4, 5] = 15
```

В итоге, как вы можете видеть, мы получили наше первое C-расширение, использующее Python.h API. Этот метод может показаться сложным, однако с практикой вы поймёте его удобство.

Из других методов встраивания C-кода в Python, можно отметить альтернативный и быстрый компилятор [Cython](http://cython.org/). Однако Cython, по сути, отличный от основной ветки Python язык, поэтому я не стал здесь его рассматривать.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://pavel-karateev.gitbook.io/intermediate-python/raznoe/python_c_extension.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
