Примечание. В этом ответе есть длинный раздел, посвященный обходным путям. Если вы просто хотите использовать это, перейдите прямо к решению 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 для указателя функции для функции 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)
, но этого будет недостаточно).
Таким образом, мы можем обойти это, написав некоторый Python C API в нашем интерфейсе 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"
Это немного некрасиво по двум причинам. Во-первых, он использует глобальную переменную (локальную для потока) для хранения вызываемого Python. Это тривиально поправимо для большинства реальных обратных вызовов, где есть аргумент void*
пользовательских данных, а также фактические входные данные для обратного вызова. В таких случаях «пользовательские данные» могут быть вызваны Python.
Вторую проблему решить немного сложнее: поскольку callable является обернутой функцией 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).
person
Flexo
schedule
09.04.2014