読者です 読者をやめる 読者になる 読者になる

cdefクラスとcpdefの挙動

cython python

Cythonでネイティブ実装のPythonクラスを作る。(Cython 0.24 で確認)

# hoge.pyx
from libc.stdio cimport printf
from libcpp.string cimport string

cdef class Hoge:

    cdef string name_

    property name:
        def __get__(self):
            return self.name_.c_str().decode()

    def __init__(self):
        self.name_ = b'josh'

    def hello(self):
        self.chello()

    cdef inline void chello(self) nogil:
        printf('my name is `%s`\n', self.name_.c_str())

    cpdef void greeting(self):
        self.chello()

    def hey(self):
        self.hello()
        self.greeting()
>>> import hoge
>>> obj = hoge.Hoge()
>>> obj.hello()
my name is `josh`
>>> obj.greeting()
my name is `josh`
>>> obj.name
'josh'
>>> obj.hey()
my name is `josh`
my name is `josh`

上記実装だとcdefで定義されたchelloやname_はPythonからアクセス出来ない。
ネイティブPythonクラスを純粋Pythonクラスから継承出来るが、逆は出来ない。



↓def宣言されたhello()のC++コード。
2つ関数があり1つ目がPythonから呼ばれる関数で2つ目がネイティブ実装。
Pythonから呼びだされたらネイティブ版へ移譲している。

/* "hoge.pyx":16
 *         self.name_ = b'josh'
 * 
 *     def hello(self):             # <<<<<<<<<<<<<<
 *         self.chello()
 * 
 */

/* Python wrapper */
static PyObject *__pyx_pw_4hoge_4Hoge_3hello(PyObject *__pyx_v_self, CYTHON_UNUSED PyObject *unused); /*proto*/
static PyObject *__pyx_pw_4hoge_4Hoge_3hello(PyObject *__pyx_v_self, CYTHON_UNUSED PyObject *unused) {
  PyObject *__pyx_r = 0;
  __Pyx_RefNannyDeclarations
  __Pyx_RefNannySetupContext("hello (wrapper)", 0);
  __pyx_r = __pyx_pf_4hoge_4Hoge_2hello(((struct __pyx_obj_4hoge_Hoge *)__pyx_v_self));

  /* function exit code */
  __Pyx_RefNannyFinishContext();
  return __pyx_r;
}

static PyObject *__pyx_pf_4hoge_4Hoge_2hello(struct __pyx_obj_4hoge_Hoge *__pyx_v_self) {
  PyObject *__pyx_r = NULL;
  __Pyx_RefNannyDeclarations
  __Pyx_RefNannySetupContext("hello", 0);

  /* "hoge.pyx":17
 * 
 *     def hello(self):
 *         self.chello()             # <<<<<<<<<<<<<<
 * 
 *     cdef inline void chello(self) nogil:
 */
  __pyx_f_4hoge_4Hoge_chello(__pyx_v_self);

  /* "hoge.pyx":16
 *         self.name_ = b'josh'
 * 
 *     def hello(self):             # <<<<<<<<<<<<<<
 *         self.chello()
 * 
 */

  /* function exit code */
  __pyx_r = Py_None; __Pyx_INCREF(Py_None);
  __Pyx_XGIVEREF(__pyx_r);
  __Pyx_RefNannyFinishContext();
  return __pyx_r;
}


↓cpdef宣言されたgreeting()のC++コード。
3つ関数があり1つ目がネイティブ実装で2つ目がPythonから呼び出される関数、3つ目の関数は2つ目に統合出来るような。分けてる意味はあるのだろうか?

/* "hoge.pyx":22
 *         printf('my name is `%s`\n', self.name_.c_str())
 * 
 *     cpdef void greeting(self):             # <<<<<<<<<<<<<<
 *         self.chello()
 * 
 */

static PyObject *__pyx_pw_4hoge_4Hoge_5greeting(PyObject *__pyx_v_self, CYTHON_UNUSED PyObject *unused); /*proto*/
static void __pyx_f_4hoge_4Hoge_greeting(struct __pyx_obj_4hoge_Hoge *__pyx_v_self, int __pyx_skip_dispatch) {
  __Pyx_RefNannyDeclarations
  PyObject *__pyx_t_1 = NULL;
  PyObject *__pyx_t_2 = NULL;
  PyObject *__pyx_t_3 = NULL;
  PyObject *__pyx_t_4 = NULL;
  __Pyx_RefNannySetupContext("greeting", 0);
  /* Check if called by wrapper */
  if (unlikely(__pyx_skip_dispatch)) ;
  /* Check if overridden in Python */
  else if (unlikely(Py_TYPE(((PyObject *)__pyx_v_self))->tp_dictoffset != 0)) {
    __pyx_t_1 = __Pyx_PyObject_GetAttrStr(((PyObject *)__pyx_v_self), __pyx_n_s_greeting); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 22, __pyx_L1_error)
    __Pyx_GOTREF(__pyx_t_1);
    if (!PyCFunction_Check(__pyx_t_1) || (PyCFunction_GET_FUNCTION(__pyx_t_1) != (PyCFunction)__pyx_pw_4hoge_4Hoge_5greeting)) {
      __Pyx_INCREF(__pyx_t_1);
      __pyx_t_3 = __pyx_t_1; __pyx_t_4 = NULL;
      if (CYTHON_COMPILING_IN_CPYTHON && unlikely(PyMethod_Check(__pyx_t_3))) {
        __pyx_t_4 = PyMethod_GET_SELF(__pyx_t_3);
        if (likely(__pyx_t_4)) {
          PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_3);
          __Pyx_INCREF(__pyx_t_4);
          __Pyx_INCREF(function);
          __Pyx_DECREF_SET(__pyx_t_3, function);
        }
      }
      if (__pyx_t_4) {
        __pyx_t_2 = __Pyx_PyObject_CallOneArg(__pyx_t_3, __pyx_t_4); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 22, __pyx_L1_error)
        __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;
      } else {
        __pyx_t_2 = __Pyx_PyObject_CallNoArg(__pyx_t_3); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 22, __pyx_L1_error)
      }
      __Pyx_GOTREF(__pyx_t_2);
      __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
      __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
      __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
      goto __pyx_L0;
    }
    __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
  }

  /* "hoge.pyx":23
 * 
 *     cpdef void greeting(self):
 *         self.chello()             # <<<<<<<<<<<<<<
 * 
 *     def hey(self):
 */
  __pyx_f_4hoge_4Hoge_chello(__pyx_v_self);

  /* "hoge.pyx":22
 *         printf('my name is `%s`\n', self.name_.c_str())
 * 
 *     cpdef void greeting(self):             # <<<<<<<<<<<<<<
 *         self.chello()
 * 
 */

  /* function exit code */
  goto __pyx_L0;
  __pyx_L1_error:;
  __Pyx_XDECREF(__pyx_t_1);
  __Pyx_XDECREF(__pyx_t_2);
  __Pyx_XDECREF(__pyx_t_3);
  __Pyx_XDECREF(__pyx_t_4);
  __Pyx_WriteUnraisable("hoge.Hoge.greeting", __pyx_clineno, __pyx_lineno, __pyx_filename, 0, 0);
  __pyx_L0:;
  __Pyx_RefNannyFinishContext();
}

/* Python wrapper */
static PyObject *__pyx_pw_4hoge_4Hoge_5greeting(PyObject *__pyx_v_self, CYTHON_UNUSED PyObject *unused); /*proto*/
static PyObject *__pyx_pw_4hoge_4Hoge_5greeting(PyObject *__pyx_v_self, CYTHON_UNUSED PyObject *unused) {
  PyObject *__pyx_r = 0;
  __Pyx_RefNannyDeclarations
  __Pyx_RefNannySetupContext("greeting (wrapper)", 0);
  __pyx_r = __pyx_pf_4hoge_4Hoge_4greeting(((struct __pyx_obj_4hoge_Hoge *)__pyx_v_self));

  /* function exit code */
  __Pyx_RefNannyFinishContext();
  return __pyx_r;
}

static PyObject *__pyx_pf_4hoge_4Hoge_4greeting(struct __pyx_obj_4hoge_Hoge *__pyx_v_self) {
  PyObject *__pyx_r = NULL;
  __Pyx_RefNannyDeclarations
  PyObject *__pyx_t_1 = NULL;
  __Pyx_RefNannySetupContext("greeting", 0);
  __Pyx_XDECREF(__pyx_r);
  __pyx_t_1 = __Pyx_void_to_None(__pyx_f_4hoge_4Hoge_greeting(__pyx_v_self, 1)); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 22, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_t_1);
  __pyx_r = __pyx_t_1;
  __pyx_t_1 = 0;
  goto __pyx_L0;

  /* function exit code */
  __pyx_L1_error:;
  __Pyx_XDECREF(__pyx_t_1);
  __Pyx_AddTraceback("hoge.Hoge.greeting", __pyx_clineno, __pyx_lineno, __pyx_filename);
  __pyx_r = NULL;
  __pyx_L0:;
  __Pyx_XGIVEREF(__pyx_r);
  __Pyx_RefNannyFinishContext();
  return __pyx_r;
}

↓hello()とgreeting()を呼び出すhey()の実装。
helloと同じでPythonから呼ばれる関数とネイティブ実装のペア。
なるほど。def宣言のhelloはPyObjectからメソッドを拾う段階でオーバーライドが解決されるが、
cpdef宣言のgreetingの方は__pyx_skip_dispatch=0にして呼び出し先でオーバーライドを解決している。

/* "hoge.pyx":25
 *         self.chello()
 * 
 *     def hey(self):             # <<<<<<<<<<<<<<
 *         self.hello()
 *         self.greeting()
 */

/* Python wrapper */
static PyObject *__pyx_pw_4hoge_4Hoge_7hey(PyObject *__pyx_v_self, CYTHON_UNUSED PyObject *unused); /*proto*/
static PyObject *__pyx_pw_4hoge_4Hoge_7hey(PyObject *__pyx_v_self, CYTHON_UNUSED PyObject *unused) {
  PyObject *__pyx_r = 0;
  __Pyx_RefNannyDeclarations
  __Pyx_RefNannySetupContext("hey (wrapper)", 0);
  __pyx_r = __pyx_pf_4hoge_4Hoge_6hey(((struct __pyx_obj_4hoge_Hoge *)__pyx_v_self));

  /* function exit code */
  __Pyx_RefNannyFinishContext();
  return __pyx_r;
}

static PyObject *__pyx_pf_4hoge_4Hoge_6hey(struct __pyx_obj_4hoge_Hoge *__pyx_v_self) {
  PyObject *__pyx_r = NULL;
  __Pyx_RefNannyDeclarations
  PyObject *__pyx_t_1 = NULL;
  PyObject *__pyx_t_2 = NULL;
  PyObject *__pyx_t_3 = NULL;
  __Pyx_RefNannySetupContext("hey", 0);

  /* "hoge.pyx":26
 * 
 *     def hey(self):
 *         self.hello()             # <<<<<<<<<<<<<<
 *         self.greeting()
 * 
 */
  __pyx_t_2 = __Pyx_PyObject_GetAttrStr(((PyObject *)__pyx_v_self), __pyx_n_s_hello); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 26, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_t_2);
  __pyx_t_3 = NULL;
  if (CYTHON_COMPILING_IN_CPYTHON && likely(PyMethod_Check(__pyx_t_2))) {
    __pyx_t_3 = PyMethod_GET_SELF(__pyx_t_2);
    if (likely(__pyx_t_3)) {
      PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_2);
      __Pyx_INCREF(__pyx_t_3);
      __Pyx_INCREF(function);
      __Pyx_DECREF_SET(__pyx_t_2, function);
    }
  }
  if (__pyx_t_3) {
    __pyx_t_1 = __Pyx_PyObject_CallOneArg(__pyx_t_2, __pyx_t_3); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 26, __pyx_L1_error)
    __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
  } else {
    __pyx_t_1 = __Pyx_PyObject_CallNoArg(__pyx_t_2); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 26, __pyx_L1_error)
  }
  __Pyx_GOTREF(__pyx_t_1);
  __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
  __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;

  /* "hoge.pyx":27
 *     def hey(self):
 *         self.hello()
 *         self.greeting()             # <<<<<<<<<<<<<<
 * 
 */
  ((struct __pyx_vtabstruct_4hoge_Hoge *)__pyx_v_self->__pyx_vtab)->greeting(__pyx_v_self, 0);

  /* "hoge.pyx":25
 *         self.chello()
 * 
 *     def hey(self):             # <<<<<<<<<<<<<<
 *         self.hello()
 *         self.greeting()
 */

  /* function exit code */
  __pyx_r = Py_None; __Pyx_INCREF(Py_None);
  goto __pyx_L0;
  __pyx_L1_error:;
  __Pyx_XDECREF(__pyx_t_1);
  __Pyx_XDECREF(__pyx_t_2);
  __Pyx_XDECREF(__pyx_t_3);
  __Pyx_AddTraceback("hoge.Hoge.hey", __pyx_clineno, __pyx_lineno, __pyx_filename);
  __pyx_r = NULL;
  __pyx_L0:;
  __Pyx_XGIVEREF(__pyx_r);
  __Pyx_RefNannyFinishContext();
  return __pyx_r;
}

ネイティブからしか呼び出さない関数はcdefで良いとしてPythonからも呼び出す場合には、
Pythonからの呼び出しが多い場合はdef、ネイティブからの呼び出しが多い場合はcpdef、が良いのかな?

cpdefにしておくとネイティブ側で関数ポインタをvtabに保持しているが、defだと保持していない。最適化のひとつとしてdef/cpdefを選択するのが良いようだ。