2025, Oct 17 05:16

Как исправить «No typemaps are defined» в SWIG: NumPy и carrays.i для массивов C в Python

SWIG выдаёт «No typemaps are defined» при массивах C в Python? Покажем, как подключить numpy.i и import_array(), либо использовать carrays.i без NumPy.

Связать C и Python через SWIG обычно несложно — пока в дело не вступают массивы. Обманчиво мелкая деталь способна вызвать пугающее предупреждение «No typemaps are defined» и помешать распознаванию аргументов-массивов. Ниже — сжатый разбор того, что именно этому предшествует, и как корректно все исправить — как с NumPy, так и без него.

Минимальный пример проблемы

У вас есть простая функция на C, вычисляющая среднеквадратичное значение числовой последовательности, и вы хотите открыть её для Python. На первый взгляд всё корректно, но SWIG предупреждает, что не может применить типмап к аргументам-массивам.

// compute.c
#include <math.h>
#include "compute.h"
double rms_val(double* buf, int count) {
    double total = 0.0;
    for (int i = 0; i < count; i++) {
        total += buf[i] * buf[i];
    }
    return sqrt(total / count);
}
// compute.h
#ifndef COMPUTE_H
#define COMPUTE_H
double rms_val(double* buf, int count);
#endif
// compute.i
%module compute
%include "typemaps.i"
%{
#include "compute.h"
%}
%apply (double *IN_ARRAY1, int DIM1) { (double *buf, int count) };
%include "compute.h"

Запуск SWIG для этого интерфейса приводит к предупреждению:

Warning 453: Can't apply (double *IN_ARRAY1,int DIM1). No typemaps are defined.

Что на самом деле не так

Идентификаторы типов IN_ARRAY1 и DIM1 не определены в typemaps.i. Их предоставляет numpy.i. Если подключить только typemaps.i, SWIG не узнает о типмапах для массивов NumPy, которые вы пытаетесь использовать, и не сможет привязать поведение массива к параметрам double* и int. В результате и появляется предупреждение, что для вашего запроса не найдено подходящих типмапов.

Решение: подключить numpy.i и инициализировать его

Чтобы использовать эти типмапы массивов, подключите numpy.i и инициализируйте C-API NumPy в сгенерированном модуле. Следующий самодостаточный интерфейс предоставляет ту же логику вычисления СКЗ и корректно настраивает работу с массивами. Для удобства тело C-функции встроено прямо в интерфейс, но при желании его можно оставить в отдельном модуле компиляции.

%module metrics
%{
#define SWIG_FILE_WITH_INIT
#include <math.h>
double rms_val(double* buf, int count) {
    double total = 0.0;
    for (int i = 0; i < count; i++) {
        total += buf[i] * buf[i];
    }
    return sqrt(total / count);
}
%}
%include "numpy.i"
%init %{
    import_array();
%}
%apply (double *IN_ARRAY1, int DIM1) { (double *buf, int count) };
double rms_val(double* buf, int count);

В Windows с инструментами Microsoft следующий makefile собирает модуль. Подправьте пути под вашу установку Python и NumPy.

all: _metrics.pyd
metrics_wrap.c metrics.py: metrics.i
	echo swigging...
	swig -python metrics.i
_metrics.pyd: metrics_wrap.c
	cl /nologo /LD /MD /W3 /Fe_metrics.pyd /Ic:\python313\include /IC:\Python313\Lib\site-packages\numpy\_core\include metrics_wrap.c -link /libpath:c:\python313\libs

После сборки импорт модуля и вызов функции с Python-последовательностью работают как ожидается:

>>> import metrics
>>> metrics.rms_val([1, 2, 3])

Альтернатива без NumPy: carrays.i

Если зависимость от NumPy не подходит, используйте вспомогательные средства SWIG из carrays.i, чтобы создавать и изменять сырые C-массивы из Python. Управление памятью будет происходить через сгенерированные функции, а не путём передачи нативных контейнеров Python.

%module rawbuf
%{
#include <math.h>
double rms_val(double* buf, int count) {
    double total = 0.0;
    for (int i = 0; i < count; i++) {
        total += buf[i] * buf[i];
    }
    return sqrt(total / count);
}
%}
%include <carrays.i>
%array_functions(double, doubleArray)
double rms_val(double* buf, int count);

Использование сгенерированных помощников из Python выглядит так:

>>> import rawbuf
>>> arr = rawbuf.new_doubleArray(3)
>>> for i in range(3):
...     rawbuf.doubleArray_setitem(arr, i, i + 1)
...
>>> rawbuf.rms_val(arr, 3)

Почему это важно

Передача массивов — ключевая развилка в связке C и Python: либо интерфейс говорит на языке, понятном Python, либо заставляет вас работать с сырой памятью. Здесь предупреждение — сигнал о том, что нужная библиотека типмапов не была подключена. Подключая numpy.i, когда вы используете IN_ARRAY1 и DIM1, или выбирая carrays.i для ручного управления, вы явно фиксируете контракт и избегаете скрытых несовпадений при генерации биндингов.

Выводы

Если SWIG сообщает, что не может применить типмап, проверьте, действительно ли используемые имена типмапов определяются подключаемыми файлами. IN_ARRAY1 и DIM1 находятся в numpy.i, а не в typemaps.i. Подключите numpy.i и инициализируйте C-API NumPy через import_array(), чтобы естественно передавать данные из Python. Если NumPy выходит за рамки проекта, carrays.i даёт аккуратный способ работать с сырыми C-массивами из Python, сохраняя полный контроль. В любом случае, как только подключён верный источник типмапов, ваша функция вычисления СКЗ — и любые похожие численные процедуры — будут вести себя предсказуемо и одинаково на разных платформах.

Статья основана на вопросе на StackOverflow от daveed krizhey и ответе от Mark Tolonen.