Cython:class 和 cdef class,使用 C++¶

class 和 cdef class¶

class 定义属性变量比较自由,cdef class 可以定义 cdef

class 使用 init 初始化,cdef class 在使用 init 之前用 cinitC 相关的参数进行初始化。

cdef class 中的方法可以是 def, cdef, cpdef 三种,只有 public 的属性才可以被访问,不可以添加新的属性。

dealloc 函数类似析构函数,负责释放申请的内存。

Cython 属性可以使用关键词 property 来定义,然后定义 getset 方法来进行获取和设置:

  1. property name:
  2. def __get__(self):
  3. return something
  4. def __set__(self):
  5. set_something

使用 C++ 类¶

使用 C++ 类时要加上 cppclass 关键词,在编译时 setup 中要加上 language="c++" 的选项。

假设我们有这样一个 C++ 类:

In [1]:

  1. %%file particle_extern.h
  2. #ifndef _PARTICLE_EXTERN_H_
  3. #define _PARTICLE_EXTERN_H_
  4.  
  5. class Particle {
  6.  
  7. public:
  8.  
  9. Particle() :
  10. mass(0), charge(0) {}
  11.  
  12. Particle(float m, float c, float *p, float *v);
  13.  
  14. ~Particle() {}
  15.  
  16. float getMass() {return mass; }
  17.  
  18. void setMass(float m) { mass = m; }
  19.  
  20. float getCharge() { return charge; }
  21.  
  22. const float *getVel() { return vel; }
  23. const float *getPos() { return pos; }
  24.  
  25. void applyImpulse(float *f, float t);
  26.  
  27. private:
  28. float mass, charge;
  29. float pos[3], vel[3];
  30. };
  31.  
  32. #endif
  1. Overwriting particle_extern.h

In [2]:

  1. %%file particle_extern.cpp
  2. #include "particle_extern.h"
  3.  
  4. Particle::Particle(float m, float c, float *p, float *v) :
  5. mass(m), charge(c)
  6. {
  7. for (int i=0; i<3; ++i) {
  8. pos[i] = p[i]; vel[i] = v[i];
  9. }
  10. }
  11.  
  12. void Particle::applyImpulse(float *f, float t)
  13. {
  14. float newvi;
  15. for(int i=0; i<3; ++i) {
  16. newvi = vel[i] + t / mass * f[i];
  17. pos[i] = (newvi + vel[i]) * t / 2.;
  18. vel[i] = newvi;
  19. }
  20. }
  1. Overwriting particle_extern.cpp

Cython 中调用这个类:

In [3]:

  1. %%file particle.pyx
  2. import numpy as np
  3.  
  4. cdef extern from "particle_extern.h":
  5.  
  6. cppclass _Particle "Particle":
  7. _Particle(float m, float c, float *p, float *v)
  8. float getMass()
  9. void setMass(float m)
  10. float getCharge()
  11. const float *getVel()
  12. const float *getPos()
  13. void applyImpulse(float *f, float t)
  14.  
  15.  
  16. cdef class Particle:
  17. cdef _Particle *thisptr # ptr to C++ instance
  18.  
  19. def __cinit__(self, m, c, float[::1] p, float[::1] v):
  20. if p.shape[0] != 3 or v.shape[0] != 3:
  21. raise ValueError("...")
  22. self.thisptr = new _Particle(m, c, &p[0], &v[0])
  23.  
  24. def __dealloc__(self):
  25. del self.thisptr
  26.  
  27. def apply_impulse(self, float[::1] v, float t):
  28. self.thisptr.applyImpulse(&v[0], t)
  29.  
  30. def __repr__(self):
  31. args = ', '.join('%s=%s' % (n, getattr(self, n)) for n in ('mass', 'charge', 'pos', 'vel'))
  32. return 'particle.Particle(%s)' % args
  33.  
  34. property charge:
  35.  
  36. def __get__(self):
  37. return self.thisptr.getCharge()
  38.  
  39. property mass: # Cython-style properties.
  40. def __get__(self):
  41. return self.thisptr.getMass()
  42.  
  43. def __set__(self, m):
  44. self.thisptr.setMass(m)
  45.  
  46. property vel:
  47.  
  48. def __get__(self):
  49. cdef const float *_vel = self.thisptr.getVel()
  50. cdef float[::1] arr = np.empty((3,), dtype=np.float32)
  51. for i in range(3):
  52. arr[i] = _vel[i]
  53. return np.asarray(arr)
  54.  
  55. property pos:
  56.  
  57. def __get__(self):
  58. cdef const float *_pos = self.thisptr.getPos()
  59. cdef float[::1] arr = np.empty((3,), dtype=np.float32)
  60. for i in range(3):
  61. arr[i] = _pos[i]
  62. return np.asarray(arr)
  1. Overwriting particle.pyx

首先从头文件声明这个类:

  1. cdef extern from "particle_extern.h":
  2.  
  3. cppclass _Particle "Particle":
  4. _Particle(float m, float c, float *p, float *v)
  5. float getMass()
  6. void setMass(float m)
  7. float getCharge()
  8. const float *getVel()
  9. const float *getPos()
  10. void applyImpulse(float *f, float t)

这里要使用 cppclass 关键词,并且为了方便,我们将 Particle 类的名字在 Cython 中重命名为 _Particle

  1. cdef class Particle:
  2. cdef _Particle *thisptr
  3. def __cinit__(self, m, c, float[::1] p, float[::1] v):
  4. if p.shape[0] != 3 or v.shape[0] != 3:
  5. raise ValueError("...")
  6. self.thisptr = new _Particle(m, c, &p[0], &v[0])

为了使用这个类,我们需要定义一个该类的指针,然后用指针指向一个 _Particle 对象。

In [4]:

  1. %%file setup.py
  2. from distutils.core import setup
  3. from distutils.extension import Extension
  4. from Cython.Distutils import build_ext
  5.  
  6. ext = Extension("particle", ["particle.pyx", "particle_extern.cpp"], language="c++")
  7.  
  8. setup(
  9. cmdclass = {'build_ext': build_ext},
  10. ext_modules = [ext],
  11. )
  1. Overwriting setup.py

要加上 language="c++" 的选项,然后编译:

In [5]:

  1. !python setup.py build_ext -i
  1. running build_ext
  2. cythoning particle.pyx to particle.cpp
  3. building 'particle' extension
  4. C:\Anaconda\Scripts\gcc.bat -DMS_WIN64 -mdll -O -Wall -IC:\Anaconda\include -IC:\Anaconda\PC -c particle.cpp -o build\temp.win-amd64-2.7\Release\particle.o
  5. C:\Anaconda\Scripts\gcc.bat -DMS_WIN64 -mdll -O -Wall -IC:\Anaconda\include -IC:\Anaconda\PC -c particle_extern.cpp -o build\temp.win-amd64-2.7\Release\particle_extern.o
  6. writing build\temp.win-amd64-2.7\Release\particle.def
  7. C:\Anaconda\Scripts\g++.bat -DMS_WIN64 -shared -s build\temp.win-amd64-2.7\Release\particle.o build\temp.win-amd64-2.7\Release\particle_extern.o build\temp.win-amd64-2.7\Release\particle.def -LC:\Anaconda\libs -LC:\Anaconda\PCbuild\amd64 -lpython27 -lmsvcr90 -o "C:\Users\lijin\Documents\Git\python-tutorial\07. interfacing with other languages\particle.pyd"
  1. particle.cpp: In function 'void __Pyx_RaiseArgtupleInvalid(const char*, int, Py_ssize_t, Py_ssize_t, Py_ssize_t)':
  2. particle.cpp:14931:59: warning: unknown conversion type character 'z' in format [-Wformat]
  3. particle.cpp:14931:59: warning: format '%s' expects argument of type 'char*', but argument 5 has type 'Py_ssize_t {aka long long int}' [-Wformat]
  4. particle.cpp:14931:59: warning: unknown conversion type character 'z' in format [-Wformat]
  5. particle.cpp:14931:59: warning: too many arguments for format [-Wformat-extra-args]
  6. particle.cpp: In function 'int __Pyx_BufFmt_ProcessTypeChunk(__Pyx_BufFmt_Context*)':
  7. particle.cpp:15498:78: warning: unknown conversion type character 'z' in format [-Wformat]
  8. particle.cpp:15498:78: warning: unknown conversion type character 'z' in format [-Wformat]
  9. particle.cpp:15498:78: warning: too many arguments for format [-Wformat-extra-args]
  10. particle.cpp:15550:67: warning: unknown conversion type character 'z' in format [-Wformat]
  11. particle.cpp:15550:67: warning: unknown conversion type character 'z' in format [-Wformat]
  12. particle.cpp:15550:67: warning: too many arguments for format [-Wformat-extra-args]
  13. particle.cpp: In function 'PyObject* __pyx_buffmt_parse_array(__Pyx_BufFmt_Context*, const char**)':
  14. particle.cpp:15612:69: warning: unknown conversion type character 'z' in format [-Wformat]
  15. particle.cpp:15612:69: warning: format '%d' expects argument of type 'int', but argument 3 has type 'size_t {aka long long unsigned int}' [-Wformat]
  16. particle.cpp:15612:69: warning: too many arguments for format [-Wformat-extra-args]
  17. particle.cpp: In function 'int __Pyx_GetBufferAndValidate(Py_buffer*, PyObject*, __Pyx_TypeInfo*, int, int, int, __Pyx_BufFmt_StackElem*)':
  18. particle.cpp:15797:73: warning: unknown conversion type character 'z' in format [-Wformat]
  19. particle.cpp:15797:73: warning: format '%s' expects argument of type 'char*', but argument 3 has type 'Py_ssize_t {aka long long int}' [-Wformat]
  20. particle.cpp:15797:73: warning: unknown conversion type character 'z' in format [-Wformat]
  21. particle.cpp:15797:73: warning: too many arguments for format [-Wformat-extra-args]
  22. particle.cpp: In function 'void __Pyx_RaiseTooManyValuesError(Py_ssize_t)':
  23. particle.cpp:16216:94: warning: unknown conversion type character 'z' in format [-Wformat]
  24. particle.cpp:16216:94: warning: too many arguments for format [-Wformat-extra-args]
  25. particle.cpp: In function 'void __Pyx_RaiseNeedMoreValuesError(Py_ssize_t)':
  26. particle.cpp:16222:48: warning: unknown conversion type character 'z' in format [-Wformat]
  27. particle.cpp:16222:48: warning: format '%s' expects argument of type 'char*', but argument 3 has type 'Py_ssize_t {aka long long int}' [-Wformat]
  28. particle.cpp:16222:48: warning: too many arguments for format [-Wformat-extra-args]
  29. particle.cpp: In function 'int __Pyx_ValidateAndInit_memviewslice(int*, int, int, int, __Pyx_TypeInfo*, __Pyx_BufFmt_StackElem*, __Pyx_memviewslice*, PyObject*)':
  30. particle.cpp:16941:50: warning: unknown conversion type character 'z' in format [-Wformat]
  31. particle.cpp:16941:50: warning: format '%s' expects argument of type 'char*', but argument 3 has type 'Py_ssize_t {aka long long int}' [-Wformat]
  32. particle.cpp:16941:50: warning: unknown conversion type character 'z' in format [-Wformat]
  33. particle.cpp:16941:50: warning: too many arguments for format [-Wformat-extra-args]

In [6]:

  1. import particle

注意这里类型要设成 float32,因为 C++ 程序中接受的是 float 类型,默认是 float64(double) 类型:

In [7]:

  1. import numpy as np
  2.  
  3. pos = vel = np.arange(3., dtype='float32')
  4. mass = 1.0
  5. charge = 2.0
  6.  
  7. p = particle.Particle(mass, charge, pos, vel)
  8. p

Out[7]:

  1. particle.Particle(mass=1.0, charge=2.0, pos=[ 0. 1. 2.], vel=[ 0. 1. 2.])

调用 apply_impulse 方法:

In [8]:

  1. p.apply_impulse(np.arange(3., dtype='float32'), 1.0)
  2.  
  3. p

Out[8]:

  1. particle.Particle(mass=1.0, charge=2.0, pos=[ 0. 1.5 3. ], vel=[ 0. 2. 4.])

查看质量:

In [9]:

  1. p.mass

Out[9]:

  1. 1.0

修改质量:

In [10]:

  1. p.mass = 3.0

查看 charge

In [11]:

  1. p.charge

Out[11]:

  1. 2.0

因为 charge 没有定义 set 方法,所以它是只读的属性,不能进行修改。

In [12]:

  1. import zipfile
  2.  
  3. f = zipfile.ZipFile('07-05-particle.zip','w',zipfile.ZIP_DEFLATED)
  4.  
  5. names = ['particle.pyx',
  6. 'particle_extern.cpp',
  7. 'particle_extern.h',
  8. 'setup.py']
  9. for name in names:
  10. f.write(name)
  11.  
  12. f.close()
  13.  
  14. !rm -f setup*.*
  15. !rm -f particle*.*
  16. !rm -rf build

原文: https://nbviewer.jupyter.org/github/lijin-THU/notes-python/blob/master/07-interfacing-with-other-languages/07.05-cython-part-3.ipynb