从语言层面来说,Cython是一种拓展的Python,其文件的扩展名为.pyx。这种类型的文件通过编译之后可以变成供Python直接调用的动态链接库(Linux/Mac下是.so,Windows下是.pyd)。根据官方文档,主要如下几编译方式:
(推荐) 通过setup.py中调用Cython.Build进行编译
使用pyximport调用.pyx文件,这种方法.pyx文件相当于普通的.py文件
在命令行使用cython命令从.pyx文件生成.c文件,再使用外部编译器将.c文件编译成Python可用的库
使用Jupyter Notebook或者Sage Notebook直接运行Cython代码
这上面四种方法里最简单的是第三种方法。运行cythonize -i <.pyx File>
即可编译.pyx成二进制库,并保存在与.pyx文件相同的目录下。cythonize命令有其他的参数,可以通过命令行查看。这个命令也可以通过python -m Cython.Build.Cythonize -i <.pyx File>
来完成。
在对Python 代码进行保护时,可以采用Cython对Python代码记性二进制转换,尽管可以对二进制文件进行逆向工程,但是依然会起到一定的保护作用.
对项目包进行编译 对以下结构的包进行编译:
在使用该方法编译的时候,一个函数只有一个参数时,有可能会出现无法找到函数的情况,鉴于这种情况,请参考复杂编译的过程。以Cython官方实例为例:
setup.py
文件:
1 2 3 4 5 6 7 from distutils.core import setupfrom Cython.Build import cythonizesetup( name = 'Hello world app' , ext_modules = cythonize("hello.py" ), )
hello.py
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 def say_hello_to (name) : print("Hello %s!" % name) ``` <!--more-->  运行`python setup.py build_ext --inplace`,生成`.so`文件,删除该文件夹中的`py文件`和`c文件`。 在终端导入使用:  * 使用`setup.py`文件进行复杂编译 ``` python from setuptools import setupfrom setuptools.extension import Extensionfrom Cython.Build import cythonizefrom Cython.Distutils import build_extsetup( ext_modules=cythonize( [ Extension('mypkg.*' , ['mypkg/*.py' ]), Extension('mypkg.submypkg1.*' , ['mypkg/submypkg1/*.py' ]), Extension('mypkg.submypkg2.*' , ['mypkg/submypkg2/*.py' ]), Extension('mypkg2.*' , ['mypkg2/*.py' ]), ], build_dir='build' , compiler_directives=dict( always_allow_keywords=True ) ), cmdclass = dict( build_ext=build_ext ), )
结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 . ├── build │ ├── mypkg │ │ ├── bar.c │ │ ├── foo.c │ │ ├── __init__.c │ │ ├── submypkg1 │ │ │ ├── bar.c │ │ │ ├── foo.c │ │ │ └── __init__.c │ │ └── submypkg2 │ │ ├── bar.c │ │ ├── foo.c │ │ └── __init__.c │ ├── mypkg2 │ │ ├── bar.c │ │ ├── foo.c │ │ └── __init__.c │ └── temp.linux-x86_64-3.7 │ └── build │ ├── mypkg │ │ ├── bar.o │ │ ├── foo.o │ │ ├── __init__.o │ │ ├── submypkg1 │ │ │ ├── bar.o │ │ │ ├── foo.o │ │ │ └── __init__.o │ │ └── submypkg2 │ │ ├── bar.o │ │ ├── foo.o │ │ └── __init__.o │ └── mypkg2 │ ├── bar.o │ ├── foo.o │ └── __init__.o ├── mypkg │ ├── bar.cpython-37m-x86_64-linux-gnu.so │ ├── bar.py │ ├── foo.cpython-37m-x86_64-linux-gnu.so │ ├── foo.py │ ├── __init__.cpython-37m-x86_64-linux-gnu.so │ ├── __init__.py │ ├── submypkg1 │ │ ├── bar.cpython-37m-x86_64-linux-gnu.so │ │ ├── bar.py │ │ ├── foo.cpython-37m-x86_64-linux-gnu.so │ │ ├── foo.py │ │ ├── __init__.cpython-37m-x86_64-linux-gnu.so │ │ └── __init__.py │ └── submypkg2 │ ├── bar.cpython-37m-x86_64-linux-gnu.so │ ├── bar.py │ ├── foo.cpython-37m-x86_64-linux-gnu.so │ ├── foo.py │ ├── __init__.cpython-37m-x86_64-linux-gnu.so │ └── __init__.py ├── mypkg2 │ ├── bar.cpython-37m-x86_64-linux-gnu.so │ ├── bar.py │ ├── foo.cpython-37m-x86_64-linux-gnu.so │ ├── foo.py │ ├── __init__.cpython-37m-x86_64-linux-gnu.so │ └── __init__.py └── setup.py
运行文件python setup.py build_ext --inplace
最终会在当前文件夹中生成build文件夹,同时在build文件夹中生成相应的文件夹和.c
文件,在主目录对应的文件夹中生成.so
文件,此时的.so
文件就可以通过导入的方式直接使用。
如果需求有改动,需要删除相应的.so文件
后重新生成该文件。
注意事项:
此always_allow_keywords=True
参数一定要添加,因为always_allow_keywords
指令禁用具有大量参数的函数只允许使用关键字参数,如果不禁用,此处在传入一个参数时会找不到对应的函数。
在使用第二种方法时,一定要注意一点,在有celery task任务的文件中,无法进行编译,否则在编译过后会出现celery无法启动的情况。
对上文出现的问题进行给出一个解答: 在上文中,如果编译了带有task任务的文件,会出现以下错误 1 AttributeError: 'method-wrapper' object has no attribute '__module__'
解决方法为:
创建第三方真实执行逻辑的文件,在进行加密时,加密第三方逻辑文件,对celery调度任务的文件不进行加密。
见官方给出的解决方式Issues ,不过此处不建议更改celery源文件。
参考链接: