使用Cython保护Python代码库
从语言层面来说,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 setup from Cython.Build import cythonize
setup( name = 'Hello world app', ext_modules = cythonize("hello.py"), )
|
hello.py文件:
1 2
| def say_hello_to(name): print("Hello %s!" % name)
|

运行python setup.py build_ext --inplace,生成.so文件,删除该文件夹中的py文件和c文件。
在终端导入使用:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| from setuptools import setup from setuptools.extension import Extension from Cython.Build import cythonize from Cython.Distutils import build_ext
setup( 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源文件。
参考链接: