最近, 做者遇到一個需求, 須要把Python的工程部署到別的集羣, 可是又要保證Python代碼的安全性. 因而上網搜索, 搜到幾個解決方案, 可是都不是符合需求. 綜合搜到的幾個解決方案, 最終做者採用了編譯成so動態庫的方式發佈.html
<!--more-->python
首先說一下搜到到幾個解決方案, 以及它們的優缺點linux
- 編譯成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
文件和例子app
setup.py文件源碼ui
#!/usr/bin/env python # -*- coding: utf-8 -*- # @File : setup.py # @Time : 2018/12/04 # @Author : spxcds (spxcds@gmail.com) 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
下面是一個例子this
目錄結構是這樣子的url
. ├── main.py ├── mypkg │ ├── foo.py │ ├── __init__.py │ └── t │ ├── __init__.py │ └── t.py └── setup.py
而後運行命令python setup.py build_ext
便可看到新的目錄結構.net
├── 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