Предаване на ограничен метод в Cython като аргумент

Опитвам се да опаковам някакъв C++ код в Cython и се сблъсках с проблем при опит да предам метод от клас като аргумент към функция.

Не знам дали го прави по-ясно, но клас A представлява статистически модел (така че myAMethod използва не само предадените аргументи, но и много променливи на екземпляр), а B има различни методи за минимизиране на предадената функция.

В C++ имам нещо от този стил: class A { public: double myAMethod(double*) }; class B { public: double myBMethod(A&, double (A::*f) (double*) }

Така че това, което се опитвам да направя, е да използвам екземпляри на A и B в кода на Cython. Нямах проблеми с опаковането на класовете, но когато се опитам да използвам myBMethod, не знам как да предам указател от вида A::*myAMethod

Ако направя това: myBMethod(ptrToAObj[0], &ptrToAObj.myAMethod), тогава Cython компилира този код до [...] &ptrToAObj->myAMethod [...] и получавам съобщението, което човек би очаквал от g++:

"ISO C++ забранява вземането на адреса на обвързана членска функция за формиране на указател към членска функция."

Но ако се опитам да посоча направо към метода на класа и направя myBMethod(ptrToAObj[0], A.myAMethod), Cython няма да компилира и ще каже, че

myAMethod не е статичен член от A.

И това е почти всичко, което успях да напредна. Бих могъл да работя на ниво C++ и да избегна някое от тези неприятности, но ако можех да използвам екземпляри на A и B в Python (чрез Cython) интерактивно, това щеше да ми помогне да ускоря темпото си на разработка. Всяка помощ ще бъде наистина оценена и се извинявам, ако на този въпрос вече е отговорено и/или е наличен в референтен справочник - търся SO, препратка към Cython и книгата "Cython" на Смит и не намерих тази тема адресирана. Благодаря предварително!


person Cristián Antuña    schedule 16.03.2015    source източник


Отговори (1)


Имам частично (ако е ужасно) решение. Готов съм да вярвам, че има по-добър начин, но не го знам.

В cpp_bit.hpp:

class A { 
  public:
    double myAMethod(double*) { return 0.0; }
}; 

typedef double (A::*A_f_ptr)(double *);

class B {
 public:
   double myBMethod(A& a, A_f_ptr f) { 
     double x = 0.1;
     return (a.*f)(&x);
   }
};

A_f_ptr getAMethod() {
  return &A::myAMethod;
}

Дадох на функциите много основни реализации, само за да мога да проверя за наистина очевидни сривове. Също така създадох указател на функция, който връща указател към myAMethod. Ще трябва да направите това за всеки метод, който искате да обвиете.

В py_bit.pyx

# distutils: language = c++

from cython.operator import dereference

cdef extern from "cpp_bit.hpp":
  cdef cppclass A:
    double myAMethod(double*)

  cdef cppclass A_f_ptr:
    pass

  cdef cppclass B:
    double myBMethod(A&, A_f_ptr)

  cdef A_f_ptr getAMethod()

cdef class PyA:
  cdef A* thisptr
  def __cinit__(self):
    self.thisptr = new A()
  def __dealloc__(self):
    del self.thisptr
  cpdef myAMethod(self,double[:] o):
    return self.thisptr.myAMethod(&o[0])

cdef class PyB:
  cdef B* thisptr
  def __cinit__(self):
    self.thisptr = new B()
  def __dealloc__(self):
    del self.thisptr
  cpdef myBMethod(self,PyA a):
    return self.thisptr.myBMethod(dereference(a.thisptr),getAMethod())

Не можах да разбера как да напиша указател на членска функция в Cython, така че вместо това създадох празен cppclass със същото име. Това работи, защото cython изглежда просто го използва за проверка на типа и нищо повече, и тъй като включва cpp_bit.hpp (където е дефиниран), можете да го използвате без проблем.

Всичко, което направих, е да оставя задачата да получа указателя на членската функция към c++ (в getAMethod, който извиквам). Не мисля, че е напълно задоволително, но изглежда работещо и е само кратка допълнителна c++ функция за всяка членска функция, до която искате да получите достъп. Можете да си поиграете с това къде сте го поставили, за да го капсулирате по-чисто.

Алтернативен, неизпробван подход: (Редактиране: по-нататъшното обмисляне предполага, че това може да е много сложно! Опитайте това на свой собствен риск!)

Лично аз бих се изкушил да променя c++ интерфейса, така че myBMethod да се дефинира като

double myBMethod(std::function<double (double*)>)

(тъй като се предполага, че винаги го извиквате с A екземпляра, с който е предаден). След това използвайте ламбда функции в c++(11!), за да обвиете екземпляра A и функцията заедно

b.myBMethod([&](double* d){ return a.myAMethod(d) };

След това може да отнеме малко изключително сложно обвиване на Cython, което все още не съм обмислял, но би трябвало да е възможно да конвертирате прост указател на функция double (double*) към обекта на функция c++ и така да го използвате по-директно.

Възможно е също така действителният ви дизайн да е по-сложен по начини, които не съм обмислял, и този подход така или иначе не е достатъчно гъвкав.

person DavidW    schedule 18.03.2015
comment
Благодаря ви много за отговора! Работи ми перфектно. Вие сте твърде груби, като го наричате ужасно! Ако бях научил нещо през краткия си период с използване на cython, е, че опаковането на C++ клас обикновено става толкова объркващо, колкото може XD. Нов съм в Python, така че не знаех дали мога да получа аналогичен тип за double (A::*) (double*), тъй като стратегията void* + getter също върши тази работа, ще я запазя. Още веднъж много благодаря! - person Cristián Antuña; 18.03.2015
comment
И така, осъзнах, че съм дал лош съвет. Интернет надеждно ме информира, че не е задължително да съхранявате указател на членска функция в void*. Както е, почти няма значение (тъй като в моя примерен код Cython използва само void* за проверка на типа) и никога за съхранение, но можете лесно да напишете код, където се обърка. Промених го, за да дефинирам празно cppclass като алтернатива на typedef, което мисля, че основно прави правилното нещо - създава тип, за който Cython знае, но не знае нищо за... - person DavidW; 18.03.2015
comment
Уау, благодаря за предупреждението! В моя код само се опитвах да заблудя Cython в неговата проверка на кода, но обикновено е по-безопасно да избягвам празни указатели (поне за мен), така че този съвет е наистина полезен. Благодаря! - person Cristián Antuña; 18.03.2015