Ответ 1
Примечание: этот ответ содержит длинный раздел об обходных решениях. Если вы просто хотите использовать этот прогон прямо к решению 5.
Проблема
Вы столкнулись с тем, что в Python все является объектом. Прежде чем мы посмотрим на фиксацию вещей, сначала дайте понять, что происходит, как есть. Я создал полный пример для работы с файлом заголовка:
double f(double x) {
return x*x;
}
double myfun(double (*f)(double x)) {
fprintf(stdout, "%g\n", f(2.0));
return -1.0;
}
typedef double (*fptr_t)(double);
fptr_t make_fptr() {
return f;
}
Основные изменения, которые я сделал до сих пор, добавляют определение к вашим объявлениям, поэтому я могу их протестировать и функцию make_fptr()
, которая возвращает что-то в Python, который, как мы знаем, будет обернут как указатель функции.
При этом первый SWIG-модуль может выглядеть так:
%module test
%{
#include "test.h"
%}
%include "test.h"
И мы можем скомпилировать его с помощью:
swig2.0 -Wall -python test.i && gcc -Wall -Wextra -I/usr/include/python2.6 -std=gnu99 -shared -o _test.so test_wrap.c
Итак, теперь мы можем запустить это и спросить Python о типах, которые у нас есть: тип test.f
и тип результата вызова test.make_fptr())
:
Python 2.6.6 (r266:84292, Dec 27 2010, 00:02:40)
[GCC 4.4.5] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import test
>>> type(test.f)
<type 'builtin_function_or_method'>
>>> repr(test.f)
'<built-in function f>'
>>> type(test.make_fptr())
<type 'SwigPyObject'>
>>> repr(test.make_fptr())
"<Swig Object of type 'fptr_t' at 0xf7428530>"
Таким образом, проблема, стоящая перед ней, должна стать ясной - нет преобразования из встроенных функций в тип SWIG для указателей функций, поэтому ваш вызов myfun(test.f)
не будет работать.
Решение
Вопрос в том, как (и где) мы это исправим? На самом деле есть, по крайней мере, четыре возможных решения, которые мы могли бы выбрать, в зависимости от того, сколько других языков вы используете, и как "Pythonic" вы хотите быть.
Решение 1:
Первое решение тривиально. Мы уже использовали test.make_fptr()
, чтобы вернуть нам дескриптор Python в указатель функции для funciton f
. Таким образом, мы можем ввести вызов:
f=test.make_fptr()
test.myfun(f)
Лично мне не очень нравится это решение, это не то, что ожидали программисты Python, и это не то, что ждут программисты C. Единственное, что нужно сделать, это простота реализации.
Решение 2:
SWIG предоставляет нам механизм для отображения указателей на целевой язык, используя %constant
. (Обычно это используется для отображения констант времени компиляции, но в любом случае все указатели функций действительно находятся в их простейшей форме).
Итак, мы можем изменить наш интерфейс интерфейса SWIG:
%module test
%{
#include "test.h"
%}
%constant double f(double);
%ignore f;
%include "test.h"
Директива %constant
сообщает SWIG об обертке f
как указателе функции, а не функции. %ignore
необходим, чтобы избежать предупреждения о просмотре нескольких версий одного и того же идентификатора.
(Примечание: я также удалил функцию typedef
и make_fptr()
из файла заголовка в этот момент)
Что теперь позволяет нам запускать:
Python 2.6.6 (r266:84292, Dec 27 2010, 00:02:40)
[GCC 4.4.5] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import test
>>> type(test.f)
<type 'SwigPyObject'>
>>> repr(test.f)
"<Swig Object of type 'double (*)(double)' at 0xf7397650>"
Отлично - он получил указатель на функцию. Но там есть зацепка с этим:
>>> test.f(0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'SwigPyObject' object is not callable
Теперь мы не можем вызвать test.f
со стороны Python. Это приводит к следующему решению:
Решение 3:
Чтобы исправить это, сначала выделим test.f
как указатель на функцию, так и встроенную функцию. Мы можем сделать это, просто используя %rename
вместо %ignore
:
% тест модуля
%{
#include "test.h"
%}
%constant double f(double);
%rename(f_call) f;
%include "test.h"
Python 2.6.6 (r266:84292, Dec 27 2010, 00:02:40)
[GCC 4.4.5] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import test
>>> repr(test.f)
"<Swig Object of type 'double (*)(double)' at 0xf73de650>"
>>> repr(test.f_call)
'<built-in function f_call>'
Это шаг, но мне все еще не нравится идея помнить, должен ли я писать test.f_call
или просто test.f
в зависимости от контекста того, что я хочу делать с f
в то время. Мы можем добиться этого, просто написав код Python в нашем интерфейсе SWIG:
%module test
%{
#include "test.h"
%}
%rename(_f_ptr) f;
%constant double f(double);
%rename(_f_call) f;
%feature("pythonprepend") myfun %{
args = f.modify(args)
%}
%include "test.h"
%pythoncode %{
class f_wrapper(object):
def __init__(self, fcall, fptr):
self.fptr = fptr
self.fcall = fcall
def __call__(self,*args):
return self.fcall(*args)
def modify(self, t):
return tuple([x.fptr if isinstance(x,self.__class__) else x for x in t])
f = f_wrapper(_f_call, _f_ptr)
%}
Здесь есть несколько функциональных бит. Во-первых, мы создаем новый, чистый класс Python для обертывания функции как вызываемого, так и указателя функции. Он содержит в качестве членов настоящий SWIG-пакет (и переименовал) функцию указателя и функции. Теперь они переименованы, чтобы начать с подчеркивания в качестве соглашения Python. Во-вторых, мы устанавливаем test.f
как экземпляр этой обертки. Когда он вызывается как функция, он передает вызов. Наконец, мы вставляем некоторый дополнительный код в оболочку myfun
для замены в указателе реальной функции, а не на нашей обертке, стараясь не изменять какие-либо другие аргументы, если они есть.
Это работает, как ожидалось, например:
import test
print "As a callable"
test.f(2.0)
print "As a function pointer"
test.myfun(test.f)
Мы могли бы сделать это немного лучше, например, с помощью макроса SWIG, чтобы избежать повторения создания экземпляра %rename
, %constant
и оболочки, но мы не можем избавиться от необходимости использовать %feature("pythonprepend")
везде, где мы передайте эти обертки обратно на SWIG. (Если это возможно сделать прозрачно, это намного выше моего знания Python).
Решение 4:
Предыдущее решение несколько опрятно, оно работает прозрачно, как и следовало ожидать (как для пользователя C и Python), так и для его механики инкапсулировано ничем, кроме Python, реализующего его.
Существует еще что-то еще, но, помимо необходимости использовать pythonprepend для каждого использования указателей на функции - если вы запустите swig -python -builtin
, это просто не сработает, потому что нет кода Python для добавления в первую очередь! (Вам нужно будет изменить конструкцию обертки: f = f_wrapper(_test._f_call, _test._f_ptr)
, но этого будет недостаточно).
Итак, мы можем обойти это, написав несколько API Python C в нашем интерфейсе SWIG:
%module test
%{
#include "test.h"
%}
%{
static __thread PyObject *callback;
static double dispatcher(double d) {
PyObject *result = PyObject_CallFunctionObjArgs(callback, PyFloat_FromDouble(d), NULL);
const double ret = PyFloat_AsDouble(result);
Py_DECREF(result);
return ret;
}
%}
%typemap(in) double(*)(double) {
if (!PyCallable_Check($input)) SWIG_fail;
$1 = dispatcher;
callback = $input;
}
%include "test.h"
Это немного уродливо по двум причинам. Во-первых, он использует глобальную переменную (thread local) для хранения вызываемого Python. Это тривиально фиксируется для большинства обратных вызовов реального мира, где есть аргумент данных пользователя void*
, а также фактические данные для обратного вызова. "Userdata" может быть Python, вызываемым в этих случаях.
Вторая проблема немного сложнее решить - потому что вызываемая - это обернутая функция C, последовательность вызовов теперь включает в себя завершение всех типов Python, а также переключение и перевод из интерпретатора Python только для того, чтобы сделать что-то, что должно быть тривиальным. Это довольно немного накладных расходов.
Мы можем работать в обратном направлении от заданного PyObject
и пытаться выяснить, какая функция (если она есть) является оболочкой для:
%module test
%{
#include "test.h"
%}
%{
static __thread PyObject *callback;
static double dispatcher(double d) {
PyObject *result = PyObject_CallFunctionObjArgs(callback, PyFloat_FromDouble(d), NULL);
const double ret = PyFloat_AsDouble(result);
Py_DECREF(result);
return ret;
}
SWIGINTERN PyObject *_wrap_f(PyObject *self, PyObject *args);
double (*lookup_method(PyObject *m))(double) {
if (!PyCFunction_Check(m)) return NULL;
PyCFunctionObject *mo = (PyCFunctionObject*)m;
if (mo->m_ml->ml_meth == _wrap_f)
return f;
return NULL;
}
%}
%typemap(in) double(*)(double) {
if (!PyCallable_Check($input)) SWIG_fail;
$1 = lookup_method($input);
if (!$1) {
$1 = dispatcher;
callback = $input;
}
}
%include "test.h"
Для этого требуется некоторый код для каждого указателя на функцию, но теперь это скорее оптимизация, чем требование, и ее можно было бы сделать более общим с помощью макроса SWIG или двух.
Решение 5:
Я работал над более быстрым 5-м решением, которое использовало бы %typemap(constcode)
, чтобы использовать %constant
как метод, так и указатель на функцию. Оказывается, хотя в SWIG уже есть поддержка для того, чтобы делать это, что я нашел при чтении некоторых источников SWIG. Итак, все, что нам нужно сделать, это просто:
%module test
%{
#include "test.h"
%}
%pythoncallback;
double f(double);
%nopythoncallback;
%ignore f;
%include "test.h"
%pythoncallback
разрешает некоторое глобальное состояние, которое заставляет последующие функции быть обернутыми, чтобы быть полезным как указателем на функцию, так и функцией! %nopythoncallback
отключает это.
Что тогда работает (с или без -builtin
) с помощью:
import test
test.f(2.0)
test.myfun(test.f)
Решает почти все проблемы за один раз. Это даже документировано в руководстве, хотя, похоже, не упоминается %pythoncallback
. Таким образом, предыдущие четыре решения в основном полезны в качестве примеров настройки интерфейсов SWIG.
Есть еще один случай, когда решение 4 было бы полезно: если вы хотите смешивать и сопоставлять обратные вызовы C и Python, вам нужно будет реализовать гибрид этих двух. (В идеале вы попытались бы сделать преобразование типа указателя функции SWIG в вашей типовой карте, а затем, если бы этот отказ был неудачным для метода PyCallable).