Catatan: jawaban ini memiliki bagian panjang tentang solusinya. Jika Anda hanya ingin menggunakan ini, langsung saja ke solusi 5.
Masalah
Anda telah menemukan fakta bahwa dalam Python semuanya adalah sebuah objek. Sebelum kita melihat cara memperbaiki sesuatu, pertama-tama mari kita pahami apa yang sebenarnya terjadi. Saya telah membuat contoh lengkap untuk digunakan, dengan file header:
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;
}
Perubahan utama yang saya buat sejauh ini adalah menambahkan definisi pada deklarasi Anda sehingga saya bisa mengujinya dan fungsi make_fptr()
yang mengembalikan sesuatu ke Python yang kita tahu akan dibungkus sebagai penunjuk fungsi.
Dengan ini modul SWIG pertama mungkin terlihat seperti:
%module test
%{
#include "test.h"
%}
%include "test.h"
Dan kita dapat mengkompilasinya dengan:
swig2.0 -Wall -python test.i && gcc -Wall -Wextra -I/usr/include/python2.6 -std=gnu99 -shared -o _test.so test_wrap.c
Jadi sekarang kita dapat menjalankan ini dan menanyakan Python tentang tipe yang kita miliki - tipe test.f
dan tipe hasil pemanggilan 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>"
Jadi masalahnya seharusnya menjadi jelas - tidak ada konversi dari fungsi bawaan ke tipe SWIG untuk penunjuk fungsi, jadi panggilan Anda ke myfun(test.f)
tidak akan berfungsi.
Solusinya
Pertanyaannya kemudian adalah bagaimana (dan di mana) kita memperbaikinya? Faktanya, setidaknya ada empat kemungkinan solusi yang dapat kami pilih, bergantung pada berapa banyak bahasa lain yang Anda targetkan dan seberapa "Pythonic" yang Anda inginkan.
Solusi 1:
Solusi pertama adalah hal yang sepele. Kami telah menggunakan test.make_fptr()
untuk mengembalikan pegangan Python ke penunjuk fungsi untuk fungsi f
. Jadi kita sebenarnya bisa menelepon:
f=test.make_fptr()
test.myfun(f)
Secara pribadi saya tidak terlalu menyukai solusi ini, ini bukan yang diharapkan oleh pemrogram Python dan bukan pula yang diharapkan oleh pemrogram C. Satu-satunya hal yang berhasil adalah kesederhanaan implementasinya.
Solusi 2:
SWIG memberi kita mekanisme untuk mengekspos penunjuk fungsi ke bahasa target, menggunakan %constant
. (Biasanya ini digunakan untuk mengekspos konstanta waktu kompilasi, tapi pada dasarnya semua penunjuk fungsi benar-benar dalam bentuk yang paling sederhana).
Jadi kita dapat memodifikasi file antarmuka SWIG kita:
%module test
%{
#include "test.h"
%}
%constant double f(double);
%ignore f;
%include "test.h"
Direktif %constant
memberitahu SWIG untuk membungkus f
sebagai penunjuk fungsi, bukan fungsi. %ignore
diperlukan untuk menghindari peringatan tentang melihat beberapa versi pengenal yang sama.
(Catatan: Saya juga menghapus fungsi typedef
dan make_fptr()
dari file header saat ini)
Yang sekarang memungkinkan kita menjalankan:
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>"
Hebat - ada penunjuk fungsinya. Tapi ada kendala dalam hal ini:
>>> test.f(0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'SwigPyObject' object is not callable
Sekarang kita tidak bisa memanggil test.f
dari sisi Python. Yang mengarah ke solusi berikutnya:
Solusi 3:
Untuk memperbaikinya, pertama-tama mari kita ekspos test.f
sebagai keduanya penunjuk fungsi dan fungsi bawaan. Kita dapat melakukannya hanya dengan menggunakan %rename
, bukan %ignore
:
%uji modul
%{
#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>'
Itu sebuah langkah, tapi saya masih tidak menyukai gagasan harus mengingat apakah saya harus menulis test.f_call
atau hanya test.f
tergantung pada konteks apa yang ingin saya lakukan dengan f
pada saat itu. Kita dapat mencapainya hanya dengan menulis beberapa kode Python di antarmuka SWIG kita:
%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)
%}
Ada beberapa bit fungsional di sini. Pertama kita membuat kelas Python murni baru untuk menggabungkan suatu fungsi sebagai callable dan penunjuk fungsi. Ini memegang sebagai anggota penunjuk dan fungsi fungsi SWIG asli yang dibungkus (dan diganti namanya). Ini sekarang diganti namanya untuk memulai dengan garis bawah sebagai konvensi Python. Kedua, kami menetapkan test.f
menjadi turunan dari pembungkus ini. Ketika dipanggil sebagai fungsi, panggilan tersebut akan diteruskan. Akhirnya kita memasukkan beberapa kode tambahan ke dalam pembungkus myfun
untuk menukar penunjuk fungsi sebenarnya daripada pembungkus kita, berhati-hatilah agar tidak mengubah argumen lain jika ada.
Ini berfungsi seperti yang diharapkan, misalnya dengan:
import test
print "As a callable"
test.f(2.0)
print "As a function pointer"
test.myfun(test.f)
Kita bisa membuat ini sedikit lebih bagus, misalnya dengan makro SWIG untuk menghindari pengulangan pembuatan instance %rename
, %constant
dan wrapper, namun kita tidak bisa lepas dari kebutuhan untuk menggunakan %feature("pythonprepend")
di mana pun kita meneruskan wrapper ini kembali ke SWIG. (Jika memungkinkan untuk melakukan itu secara transparan, itu di luar pengetahuan Python saya).
Solusi 4:
Solusi sebelumnya agak lebih rapi, ia bekerja secara transparan seperti yang Anda harapkan (baik sebagai pengguna C dan Python) dan mekanismenya dienkapsulasi hanya dengan implementasi Python.
Namun masih ada masalah, selain kebutuhan untuk menggunakan pythonprepend untuk setiap penggunaan pointer fungsi - jika Anda menjalankan swig -python -builtin
itu tidak akan berfungsi, karena tidak ada kode Python untuk ditambahkan di tempat pertama! (Anda perlu mengubah konstruksi pembungkusnya menjadi: f = f_wrapper(_test._f_call, _test._f_ptr)
, tetapi itu tidak cukup).
Jadi kita bisa menyiasatinya dengan menulis beberapa API Python C di antarmuka SWIG kita:
%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"
Ini agak jelek karena dua alasan. Pertama, ia menggunakan variabel global (thread lokal) untuk menyimpan callable Python. Hal ini dapat diperbaiki dengan mudah untuk sebagian besar panggilan balik dunia nyata, di mana terdapat argumen data pengguna void*
serta masukan aktual ke panggilan balik tersebut. "Data pengguna" dapat berupa callable Python dalam kasus tersebut.
Masalah kedua sedikit lebih rumit untuk dipecahkan - karena callable adalah fungsi C yang dibungkus, urutan panggilan sekarang melibatkan membungkus semuanya sebagai tipe Python dan perjalanan bolak-balik dari interpreter Python hanya untuk melakukan sesuatu yang seharusnya sepele. Itu biaya overhead yang cukup besar.
Kita dapat bekerja mundur dari PyObject
tertentu dan mencoba mencari tahu fungsi mana (jika ada) yang menjadi pembungkusnya:
%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"
Ini memang memerlukan beberapa kode penunjuk per fungsi, tetapi sekarang ini merupakan pengoptimalan daripada persyaratan dan dapat dibuat lebih umum dengan satu atau dua makro SWIG.
Solusi 5:
Saya sedang mengerjakan solusi ke-5 yang lebih rapi yang akan menggunakan %typemap(constcode)
untuk memungkinkan %constant
digunakan sebagai metode dan penunjuk fungsi. Ternyata di SWIG sudah ada dukungan untuk melakukan hal tersebut, yang saya temukan saat membaca beberapa sumber SWIG. Jadi sebenarnya yang perlu kita lakukan hanyalah:
%module test
%{
#include "test.h"
%}
%pythoncallback;
double f(double);
%nopythoncallback;
%ignore f;
%include "test.h"
%pythoncallback
mengaktifkan beberapa keadaan global yang menyebabkan fungsi-fungsi selanjutnya dibungkus agar dapat digunakan baik sebagai penunjuk fungsi maupun fungsi! %nopythoncallback
menonaktifkannya.
Yang kemudian berfungsi (dengan atau tanpa -builtin
) dengan:
import test
test.f(2.0)
test.myfun(test.f)
Yang menyelesaikan hampir semua masalah sekaligus. Hal ini bahkan didokumentasikan dalam manual juga, meskipun tampaknya tidak disebutkan sama sekali %pythoncallback
. Jadi empat solusi sebelumnya sebagian besar hanya berguna sebagai contoh penyesuaian antarmuka SWIG.
Masih ada satu kasus di mana solusi 4 akan berguna - jika Anda ingin memadupadankan callback yang diimplementasikan C dan Python, Anda perlu mengimplementasikan gabungan keduanya. (Idealnya Anda akan mencoba dan melakukan konversi tipe penunjuk fungsi SWIG di peta ketik Anda dan kemudian melakukan fallback yang gagal ke metode PyCallable sebagai gantinya).
person
Flexo
schedule
09.04.2014