# Поширені запитання про розширення/вбудовування

Зміст

- Поширені запитання про розширення/вбудовування

  - Чи можу я створювати власні функції на C?

  - Чи можу я створювати власні функції на C++?

  - Писати С важко; є якісь альтернативи?

  - Як я можу виконати довільні оператори Python із C?

  - Як я можу обчислити довільний вираз Python із C?

  - Як отримати значення C з об’єкта Python?

  - Як я можу використовувати Py_BuildValue() для створення кортежу довільної довжини?

  - Як викликати метод об’єкта з C?

  - Як мені перехопити вихідні дані PyErr_Print() (або будь-чого, що друкує в stdout/stderr)?

  - Як отримати доступ до модуля, написаного мовою Python, із C?

  - Як підключитися до об’єктів C++ із Python?

  - Я додав модуль за допомогою файлу Setup, і отримав помилку, чому?

  - Як відлагодити розширення?

  - Я хочу скомпілювати модуль Python у своїй системі Linux, але деякі файли відсутні. чому

  - Як відрізнити «неповне введення» від «некоректного введення»?

  - Як знайти невизначені символи g++ __builtin_new або __pure_virtual?

  - Чи можу я створити клас об’єктів за допомогою деяких методів, реалізованих у C, а інших – у Python (наприклад, через успадкування)?

## Чи можу я створювати власні функції на C?

Так, у C можна створювати вбудовані модулі, що містять функції, змінні, винятки та навіть нові типи. Це пояснюється в документі Розширення та вбудовування інтерпретатора Python.

Більшість книжок про Python середнього та просунутого рівня також охоплюють цю тему.

## Чи можу я створювати власні функції на C++?

Так, використовуючи функції сумісності з C, наявні в C++. Розмістіть `extern "C" { ... }` навколо файлів включення Python і поставте `extern "C"` перед кожною функцією, яку буде викликати інтерпретатор Python. Глобальні чи статичні об’єкти C++ із конструкторами, мабуть, не є гарною ідеєю.

## Писати С важко; є якісь альтернативи?

Існує кілька альтернатив написанню власних розширень C, залежно від того, що ви намагаєтеся зробити.

Cython and its relative Pyrex are compilers that accept a slightly modified form of Python and generate the corresponding C code. Cython and Pyrex make it possible to write an extension without having to learn Python’s C API.

If you need to interface to some C or C++ library for which no Python extension currently exists, you can try wrapping the library’s data types and functions with a tool such as SWIG. SIP, CXX Boost, or Weave are also alternatives for wrapping C++ libraries.

## Як я можу виконати довільні оператори Python із C?

Функція найвищого рівня для цього — `PyRun_SimpleString()`, яка приймає один рядковий аргумент для виконання в контексті модуля `__main__` і повертає `0` для успіху та `- 1`, коли стався виняток (включаючи `SyntaxError`). Якщо вам потрібен більше контролю, використовуйте `PyRun_String()`; див. джерело для `PyRun_SimpleString()` у `Python/pythonrun.c`.

## Як я можу обчислити довільний вираз Python із C?

Викличте функцію `PyRun_String()` з попереднього запитання з символом початку `Py_eval_input`; він аналізує вираз, обчислює його та повертає його значення.

## Як отримати значення C з об’єкта Python?

That depends on the object’s type. If it’s a tuple, `PyTuple_Size()` returns its length and `PyTuple_GetItem()` returns the item at a specified index. Lists have similar functions, `PyListSize()` and `PyList_GetItem()`.

For bytes, `PyBytes_Size()` returns its length and `PyBytes_AsStringAndSize()` provides a pointer to its value and its length. Note that Python bytes objects may contain null bytes so C’s `strlen()` should not be used.

Щоб перевірити тип об’єкта, спочатку переконайтеся, що він не `NULL`, а потім скористайтеся `PyBytes_Check()`, `PyTuple_Check()`, `PyList_Check()` і т.д.

Існує також високорівневий API для об’єктів Python, який надається так званим «абстрактним» інтерфейсом — читайте `Include/abstract.h` для отримання додаткової інформації. Він дозволяє взаємодіяти з будь-якою послідовністю Python за допомогою таких викликів, як `PySequence_Length()`, `PySequence_GetItem()` тощо, а також багатьох інших корисних протоколів, таких як числа (`PyNumber_Index()` та ін.) і відображення в API PyMapping.

## Як я можу використовувати Py_BuildValue() для створення кортежу довільної довжини?

Ви не можете це робити. Натомість використовуйте `PyTuple_Pack()`.

## Як викликати метод об’єкта з C?

Функцію `PyObject_CallMethod()` можна використовувати для виклику довільного методу об’єкта. Параметрами є об’єкт, ім’я методу для виклику, рядок формату, який використовується з `Py_BuildValue()`, і значення аргументів:

    PyObject *
    PyObject_CallMethod(PyObject *object, const char *method_name,
                        const char *arg_format, ...);

Це працює для будь-якого об’єкта, який має методи – вбудовані чи визначені користувачем. Ви несете відповідальність за остаточне `Py_DECREF()`„виведення значення, що повертається.

Щоб викликати, наприклад, метод «seek» файлового об’єкта з аргументами 10, 0 (припускаючи, що вказівник на файловий об’єкт є «f»):

    res = PyObject_CallMethod(f, "seek", "(ii)", 10, 0);
    if (res == NULL) {
            ... an exception occurred ...
    }
    else {
            Py_DECREF(res);
    }

Зауважте, що оскільки `PyObject_CallObject()` завжди вимагає кортеж для списку аргументів, щоб викликати функцію без аргументів, передайте «()» для формату, а щоб викликати функцію з одним аргументом, оточіть аргумент в дужках, напр. «(i)».

## Як мені перехопити вихідні дані PyErr_Print() (або будь-чого, що друкує в stdout/stderr)?

У коді Python визначте об’єкт, який підтримує метод `write()`. Призначте цей об’єкт `sys.stdout` і `sys.stderr`. Викличте print_error або просто дозвольте стандартному механізму відстеження працювати. Тоді вивід буде відправлятися туди, куди його надсилає метод `write()`.

Найпростіший спосіб зробити це — використати клас `io.StringIO`:

    >>> import io, sys
    >>> sys.stdout = io.StringIO()
    >>> print('foo')
    >>> print('hello world!')
    >>> sys.stderr.write(sys.stdout.getvalue())
    foo
    hello world!

Настроюваний об’єкт, який буде робити те саме, виглядатиме так:

    >>> import io, sys
    >>> class StdoutCatcher(io.TextIOBase):
    ...     def __init__(self):
    ...         self.data = []
    ...     def write(self, stuff):
    ...         self.data.append(stuff)
    ...
    >>> import sys
    >>> sys.stdout = StdoutCatcher()
    >>> print('foo')
    >>> print('hello world!')
    >>> sys.stderr.write(''.join(sys.stdout.data))
    foo
    hello world!

## Як отримати доступ до модуля, написаного мовою Python, із C?

Ви можете отримати вказівник на об’єкт модуля наступним чином:

    module = PyImport_ImportModule("<modulename>");

Якщо модуль ще не було імпортовано (тобто його ще немає в `sys.modules`), це ініціалізує модуль; інакше він просто повертає значення `sys.modules[" <modulename> "]`. Зверніть увагу, що він не вводить модуль у простір імен — він лише гарантує, що його було ініціалізовано та збережено в `sys.modules`.

Потім ви можете отримати доступ до атрибутів модуля (тобто до будь-якого імені, визначеного в модулі) наступним чином:

    attr = PyObject_GetAttrString(module, "<attrname>");

Виклик `PyObject_SetAttrString()` для призначення змінним у модулі також працює.

## Як підключитися до об’єктів C++ із Python?

Залежно від ваших вимог існує багато підходів. Щоб зробити це вручну, почніть із прочитання документу «Розширення та вбудовування. Зрозумійте, що для системи виконання Python немає великої різниці між C і C++, тому стратегія побудови нового типу Python навколо типу структури (вказівника) C також працюватиме для об’єктів C++.

Для бібліотек C++ див. Писати С важко; є якісь альтернативи?.

## Я додав модуль за допомогою файлу Setup, і отримав помилку, чому?

Налаштування має закінчуватися символом нового рядка, якщо там немає нового рядка, процес збирання завершується помилкою. (Щоб виправити це, потрібне негарне хакерство сценарію оболонки, і ця помилка настільки незначна, що здається не вартою зусиль.)

## Як відлагодити розширення?

Під час використання GDB із динамічно завантажуваними розширеннями ви не можете встановити точку зупинки у своєму розширенні, доки воно не завантажиться.

У свій файл `.gdbinit` (або інтерактивно) додайте команду:

    br _PyImport_LoadDynamicModule

Тоді, коли ви запускаєте GDB:

    $ gdb /local/bin/python
    gdb) run myscript.py
    gdb) continue # repeat until your extension is loaded
    gdb) finish   # so that your extension is loaded
    gdb) br myfunction.c:50
    gdb) continue

## Я хочу скомпілювати модуль Python у своїй системі Linux, але деякі файли відсутні. чому

Most packaged versions of Python don’t include the `/usr/lib/python2.x/config/` directory, which contains various files required for compiling Python extensions.

For Red Hat, install the python-devel RPM to get the necessary files.

For Debian, run `apt-get install python-dev`.

## Як відрізнити «неповне введення» від «некоректного введення»?

Іноді потрібно імітувати поведінку інтерактивного інтерпретатора Python, коли він дає вам запит на продовження, коли введення неповне (наприклад, ви ввели початок оператора «if» або не закрили дужки чи потрійні рядкові лапки), але він дає вам повідомлення про синтаксичну помилку негайно, коли введення некректне.

У Python ви можете використовувати модуль `codeop`, який достатньо наближає поведінку аналізатора. IDLE використовує це, наприклад.

Найпростіший спосіб зробити це в C — викликати `PyRun_InteractiveLoop()` (можливо, в окремому потоці) і дозволити інтерпретатору Python обробити вхідні дані за вас. Ви також можете встановити `PyOS_ReadlineFunctionPointer()` так, щоб він вказував на вашу власну функцію введення. Перегляньте `Modules/readline.c` і `Parser/myreadline.c` для отримання додаткових підказок.

## Як знайти невизначені символи g++ __builtin_new або __pure_virtual?

Щоб динамічно завантажувати модулі розширення g++, ви повинні перекомпілювати Python, перекомпілювати його за допомогою g++ (змінити LINKCC у Makefile модулів Python) і пов’язати свій модуль розширення за допомогою g++ (наприклад, `g++ -shared -o mymodule.so mymodule.o`).

## Чи можу я створити клас об’єктів за допомогою деяких методів, реалізованих у C, а інших – у Python (наприклад, через успадкування)?

Так, ви можете успадкувати такі вбудовані класи, як `int`, `list`, `dict` тощо.

The Boost Python Library (BPL, https://www.boost.org/libs/python/doc/index.html) provides a way of doing this from C++ (i.e. you can inherit from an extension class written in C++ using the BPL).