Whoosy's Blog

藏巧于拙 用晦而明 寓清于浊 以屈为伸

0%

使用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代码记性二进制转换,尽管可以对二进制文件进行逆向工程,但是依然会起到一定的保护作用.

对项目包进行编译

对以下结构的包进行编译:

tree

  • 通过setup.py文件进行简单编译

在使用该方法编译的时候,一个函数只有一个参数时,有可能会出现无法找到函数的情况,鉴于这种情况,请参考复杂编译的过程。以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
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-->


![example1](/images/Cython_example1.png)

运行`python setup.py build_ext --inplace`,生成`.so`文件,删除该文件夹中的`py文件`和`c文件`。
在终端导入使用:
![example2](/images/Cython_example2.png)

* 使用`setup.py`文件进行复杂编译

``` python
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源文件。

参考链接: