最近,作者遇到了一个需要将Python工程引入另一个集群,但必须确保Python代码安全的需求。 于是在网上搜索,找到了几种解决方案,但都不符合需求。 综合检索了几种解决方案,最终作者以编译成so动态库的方式公开。
首先,找到了一些解决方案及其优缺点
编译成pyc发布
优点:操作简单
缺点:可以反编译
cx_freeze
好处:在部署到新环境中时非常有用,因为freeze命令可以直接在二进制中生成项目的所有依赖关系
缺点: freeze命令在工程项目大时非常慢,其生成的Python代码实际上也是pyc,可以反向编译
极简
优点:代码混淆保护代码
缺点:似乎只影响单个文件的混淆,但在一个工程项目中行不通
cython被编译成动态库
优点可以将:代码编译成. so动态库,起到代码保护的作用
缺点:编译速度太慢了
综合这些优缺点,作者最终选择了用cython编译成动态库的方式,达到了保护Python代码的目的,cython官方文档
:说明具体做法和原理
cython首先将python代码翻译成c语言代码,然后cython将其编译成. so动态库。 最后,使用编译的build/lib.linux-x86_64-2.7 (此目录因平台和python版本而异。 作者的是linux平台,python版本
但是,这里有个洞。 编译Python库时,build/lib.linux-x86_64-2.7中的库文件需要每个库的__init__.py文件。 因此,以下代码首先创建空的__文件,然后查找所有. py文件并将其编译到动态库中,并将除. py之外的所有文件移动到原始目录中的相应位置。 以下是相应的转换后的setup.py文件和示例
setup.py文件的源代码
#! /usr/易怒的时间/env python
#-* -编码: utf-8-* -
# @File : setup.py
# @Time : 2018/12/04
# @ author : spx CDs (spx CDs @ Gmail.com) )。
导入操作系统
导入系统
导入shutil
导入编号
导入模板文件
来自设置工具导入设置
from setup tools.extensionimportextension
fromcython.buildimportcythonize
fromcython.distutilsimportbuild _ ext
导入平台
build _ root _ dir=‘build/lib .‘platform.system (.lower )、‘-‘platform.machine’和‘-‘str’。
sys.version _ info.major (.‘str’sys.version _ info.major ) )。
打印(build _ root _ dir )是
扩展=[ ]
ignore _ folders=[‘build’,‘test’,‘tests’]
conf_folders=[‘conf‘]
defget _ root _ path (根) :
ifOS.path.dirname(root ) in )、)、)、) :
return OS.path.basename (根)
else:
返回get _ root _ path (OS.path.dirname ) root ) )
defcopy_file(src,dest ) :
ifOS.path.exists(dest ) :
返回
ifno tos.path.exists (OS.path.dirname ) dest ) ) :
操作系统. makedirs (操作系统. path.dirname ) (dest ) )
IFOS.Path.Isdir(src ) :
shutil.copytree(src,dest ) )。
else:
shutil.copyfile(src,dest ) )。
def touch_init_file () :
init _ file _ name=OS.path.join (tem
pfile.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