Cython:Cython 语法,调用其他C库

Cython 语法

cdef 关键词

cdef 定义 C 类型变量。

可以定义局部变量:

  1. def fib(int n):
  2. cdef int a,b,i
  3. ...

定义函数返回值:

  1. cdef float distance(float *x, float *y, int n):
  2. cdef:
  3. int i
  4. float d = 0.0
  5. for i in range(n):
  6. d += (x[i] - y[i]) ** 2
  7. return d

定义函数:

  1. cdef class Particle(object):
  2. cdef float psn[3], vel[3]
  3. cdef int id

注意函数的参数不需要使用 cdef 的定义。

def, cdef, cpdef 函数

Cython 一共有三种定义方式,def, cdef, cpdef 三种:

  • def - Python, Cython 都可以调用
  • cdef - 更快,只能 Cython 调用,可以使用指针
  • cpdef - Python, Cython 都可以调用,不能使用指针

cimport

In [1]:

  1. from math import sin as pysin
  2. from numpy import sin as npsin

In [2]:

  1. %load_ext Cython

从标准 C 语言库中调用模块,cimport 只能在 Cython 中使用:

In [3]:

  1. %%cython
  2. from libc.math cimport sin
  3. from libc.stdlib cimport malloc, free

cimport 和 pxd 文件

如果想在多个文件中复用 Cython 代码,可以定义一个 .pxd 文件(相当于头文件 .h)定义方法,这个文件对应于一个 .pyx 文件(相当于源文件 .c),然后在其他的文件中使用 cimport 导入:

fib.pxd, fib.pyx 文件存在,那么可以这样调用:

  1. from fib cimport fib

还可以调用 C++ 标准库和 Numpy C Api 中的文件:

  1. from libcpp.vector cimport vector
  2. cimport numpy as cnp

调用其他C库

从标准库 string.h 中调用 strlen

In [4]:

  1. %%file len_extern.pyx
  2. cdef extern from "string.h":
  3. int strlen(char *c)
  4.  
  5. def get_len(char *message):
  6. return strlen(message)
  1. Writing len_extern.pyx

不过 Cython 不会自动扫描导入的头文件,所以要使用的函数必须再声明一遍:

In [5]:

  1. %%file setup_len_extern.py
  2. from distutils.core import setup
  3. from distutils.extension import Extension
  4. from Cython.Distutils import build_ext
  5.  
  6. setup(
  7. ext_modules=[ Extension("len_extern", ["len_extern.pyx"]) ],
  8. cmdclass = {'build_ext': build_ext}
  9. )
  1. Writing setup_len_extern.py

编译:

In [6]:

  1. !python setup_len_extern.py build_ext --inplace
  1. running build_ext
  2. cythoning len_extern.pyx to len_extern.c
  3. building 'len_extern' extension
  4. creating build
  5. creating build\temp.win-amd64-2.7
  6. creating build\temp.win-amd64-2.7\Release
  7. C:\Miniconda\Scripts\gcc.bat -DMS_WIN64 -mdll -O -Wall -IC:\Miniconda\include -IC:\Miniconda\PC -c len_extern.c -o build\temp.win-amd64-2.7\Release\len_extern.o
  8. writing build\temp.win-amd64-2.7\Release\len_extern.def
  9. C:\Miniconda\Scripts\gcc.bat -DMS_WIN64 -shared -s build\temp.win-amd64-2.7\Release\len_extern.o build\temp.win-amd64-2.7\Release\len_extern.def -LC:\Miniconda\libs -LC:\Miniconda\PCbuild\amd64 -lpython27 -lmsvcr90 -o "C:\Users\Jin\Documents\Git\python-tutorial\07. interfacing with other languages\len_extern.pyd"

Python 中调用:

In [7]:

  1. import len_extern

调用这个模块后,并不能直接使用 strlen 函数,可以看到,这个模块中并没有 strlen 这个函数:

In [8]:

  1. dir(len_extern)

Out[8]:

  1. ['__builtins__',
  2. '__doc__',
  3. '__file__',
  4. '__name__',
  5. '__package__',
  6. '__test__',
  7. 'get_len']

不过可以调用 get_len 函数:

In [9]:

  1. len_extern.get_len('hello')

Out[9]:

  1. 5

因为调用的是 C 函数,所以函数的表现与 C 语言的用法一致,例如 C 语言以 \0 为字符串的结束符,所以会出现这样的情况:

In [10]:

  1. len_extern.get_len('hello\0world!')

Out[10]:

  1. 5

除了对已有的 C 函数进行调用,还可以对已有的 C 结构体进行调用和修改:

In [11]:

  1. %%file time_extern.pyx
  2. cdef extern from "time.h":
  3.  
  4. struct tm:
  5. int tm_mday
  6. int tm_mon
  7. int tm_year
  8.  
  9. ctypedef long time_t
  10. tm* localtime(time_t *timer)
  11. time_t time(time_t *tloc)
  12.  
  13. def get_date():
  14. """Return a tuple with the current day, month and year."""
  15. cdef time_t t
  16. cdef tm* ts
  17. t = time(NULL)
  18. ts = localtime(&t)
  19. return ts.tm_mday, ts.tm_mon + 1, ts.tm_year + 1900
  1. Writing time_extern.pyx

这里我们只使用 tm 结构体的年月日信息,所以只声明了要用了三个属性。

In [12]:

  1. %%file setup_time_extern.py
  2. from distutils.core import setup
  3. from distutils.extension import Extension
  4. from Cython.Distutils import build_ext
  5.  
  6. setup(
  7. ext_modules=[ Extension("time_extern", ["time_extern.pyx"]) ],
  8. cmdclass = {'build_ext': build_ext}
  9. )
  1. Writing setup_time_extern.py

编译:

In [13]:

  1. !python setup_time_extern.py build_ext --inplace
  1. running build_ext
  2. cythoning time_extern.pyx to time_extern.c
  3. building 'time_extern' extension
  4. C:\Miniconda\Scripts\gcc.bat -DMS_WIN64 -mdll -O -Wall -IC:\Miniconda\include -IC:\Miniconda\PC -c time_extern.c -o build\temp.win-amd64-2.7\Release\time_extern.o
  5. writing build\temp.win-amd64-2.7\Release\time_extern.def
  6. C:\Miniconda\Scripts\gcc.bat -DMS_WIN64 -shared -s build\temp.win-amd64-2.7\Release\time_extern.o build\temp.win-amd64-2.7\Release\time_extern.def -LC:\Miniconda\libs -LC:\Miniconda\PCbuild\amd64 -lpython27 -lmsvcr90 -o "C:\Users\Jin\Documents\Git\python-tutorial\07. interfacing with other languages\time_extern.pyd"

测试:

In [14]:

  1. import time_extern
  2.  
  3. time_extern.get_date()

Out[14]:

  1. (19, 9, 2015)

清理文件:

In [15]:

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

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