Python 專案轉.so動態庫
阿新 • • 發佈:2019-01-01
最近, 作者遇到一個需求, 需要把Python的工程部署到別的叢集, 但是又要保證Python程式碼的安全性. 於是上網搜尋, 搜到幾個解決方案, 但是都不是符合需求. 綜合搜到的幾個解決方案, 最終作者採用了編譯成so動態庫的方式釋出.
首先說一下搜到到幾個解決方案, 以及它們的優缺點
- 編譯成pyc釋出
優點: 操作簡單
缺點: 可以被反編譯 - cx_freeze
優點: 可以通過freeze命令直接把一個專案所有的依賴生成一個二進位制, 所以部署到新的環境時, 十分方便
缺點: freeze命令如果工程專案很大的話, 速度非常慢, 而且其生成的Python程式碼其實也是pyc, 可以被反編譯 - pyminifier
優點: 通過程式碼混淆的方式保護程式碼的安全
缺點: 貌似, 只對單個檔案的混淆其作用, 如果是一個工程專案就不好使了 - cython編譯成動態庫
優點: 可以將程式碼編譯成.so動態庫, 起到程式碼保護的作用
缺點: 編譯速度太慢了
綜合以上幾個優缺點, 作者最終選擇了通過cython編譯成動態庫的方式, 來達到保護Python程式碼的目的, cython官方文件
說下具體的做法和原理:
cython首先會把python程式碼翻譯成C語言程式碼, 然後cython在將其編譯成.so動態庫, 最後, 在編譯好的build/lib.linux-x86_64-2.7(不同的平臺和python版本這個目錄是不一樣, 作者的是linux平臺, Python2.7版本)資料夾中, 直接引用即可.
但是這裡有一個坑, 如果你編譯的是一個Python的庫, 那麼你的build/lib.linux-x86_64-2.7中的庫檔案中, 每個庫裡必須有一個__init__.py
__init__.py
檔案拷貝到對應的庫中的操作, 然後搜尋所有的.py檔案, 將其編譯成動態庫, 然後把所有的非.py檔案, 移動到原目錄對應的位置. 下面是對應的轉換的setup.py
檔案和例子
setup.py檔案原始碼
#!/usr/bin/env python # -*- coding: utf-8 -*- # @File : setup.py # @Time : 2018/12/04 # @Author : spxcds ([email protected]) import os import sys import shutil import numpy import tempfile from setuptools import setup from setuptools.extension import Extension from Cython.Build import cythonize from Cython.Distutils import build_ext import platform build_root_dir = 'build/lib.' + platform.system().lower() + '-' + platform.machine() + '-' + str( sys.version_info.major) + '.' + str(sys.version_info.minor) print(build_root_dir) extensions = [] ignore_folders = ['build', 'test', 'tests'] conf_folders = ['conf'] def get_root_path(root): if os.path.dirname(root) in ['', '.']: return os.path.basename(root) else: return get_root_path(os.path.dirname(root)) def copy_file(src, dest): if os.path.exists(dest): return if not os.path.exists(os.path.dirname(dest)): os.makedirs(os.path.dirname(dest)) if os.path.isdir(src): shutil.copytree(src, dest) else: shutil.copyfile(src, dest) def touch_init_file(): init_file_name = os.path.join(tempfile.mkdtemp(), '__init__.py') with open(init_file_name, 'w'): pass return init_file_name init_file = touch_init_file() print(init_file) def compose_extensions(root='.'): for file_ in os.listdir(root): abs_file = os.path.join(root, file_) if os.path.isfile(abs_file): if abs_file.endswith('.py'): extensions.append(Extension(get_root_path(abs_file) + '.*', [abs_file])) elif abs_file.endswith('.c') or abs_file.endswith('.pyc'): continue else: copy_file(abs_file, os.path.join(build_root_dir, abs_file)) if abs_file.endswith('__init__.py'): copy_file(init_file, os.path.join(build_root_dir, abs_file)) else: if os.path.basename(abs_file) in ignore_folders: continue if os.path.basename(abs_file) in conf_folders: copy_file(abs_file, os.path.join(build_root_dir, abs_file)) compose_extensions(abs_file) compose_extensions() os.remove(init_file) setup( name='my_project', version='1.0', ext_modules=cythonize( extensions, nthreads=16, compiler_directives=dict(always_allow_keywords=True), include_path=[numpy.get_include()]), cmdclass=dict(build_ext=build_ext)) # python setup.py build_ext
下面是一個例子
目錄結構是這樣子的
.
├── main.py
├── mypkg
│ ├── foo.py
│ ├── __init__.py
│ └── t
│ ├── __init__.py
│ └── t.py
└── setup.py
然後執行命令python setup.py build_ext
即可看到新的目錄結構
├── build
│ ├── lib.linux-x86_64-2.7
│ │ ├── main.so
│ │ ├── mypkg
│ │ │ ├── foo.so
│ │ │ ├── __init__.py
│ │ │ ├── __init__.so
│ │ │ └── t
│ │ │ ├── __init__.py
│ │ │ ├── __init__.so
│ │ │ └── t.so
│ │ └── setup.so
│ └── temp.linux-x86_64-2.7
│ ├── main.o
│ ├── mypkg
│ │ ├── foo.o
│ │ ├── __init__.o
│ │ └── t
│ │ ├── __init__.o
│ │ └── t.o
│ └── setup.o
├── main.c
├── main.py
├── mypkg
│ ├── foo.c
│ ├── foo.py
│ ├── __init__.c
│ ├── __init__.py
│ └── t
│ ├── __init__.c
│ ├── __init__.py
│ ├── t.c
│ └── t.py
├── setup.c
└── setup.py
然後, 將main.py
拷貝到build/lib.linux-x86_64-2.7
直接就可以運行了
.
├── main.py
├── main.so
├── mypkg
│ ├── foo.so
│ ├── __init__.py
│ ├── __init__.so
│ └── t
│ ├── __init__.py
│ ├── __init__.so
│ └── t.so
└── setup.so
$ cat main.py
from mypkg.foo import hello
from mypkg import fun1
from mypkg.t.t import t
if __name__ == '__main__':
hello()
fun1()
t()
$ python main.py
this is in hello
this is in fun1
this is in t