python代码打包成动态库_Python项⽬转.so动态库
最近, 作者遇到⼀个需求, 需要把Python的⼯程部署到别的集, 但是⼜要保证Python代码的安全性. 于是上⽹搜索, 搜到⼏个解决⽅案, 但是都不是符合需求. 综合搜到的⼏个解决⽅案, 最终作者采⽤了编译成so动态库的⽅式发布.
⾸先说⼀下搜到到⼏个解决⽅案, 以及它们的优缺点
编译成pyc发布
优点: 操作简单
缺点: 可以被反编译
cx_freeze
python代码转换优点: 可以通过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 (spxcds@gmail)
import os
import sys
import shutil
import numpy
import tempfile
from setuptools import setup
sion 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 ists(dest):
return
if not ists(os.path.dirname(dest)):
os.makedirs(os.path.dirname(dest))
if os.path.isdir(src):
else:
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 dswith('.py'):
extensions.append(Extension(get_root_path(abs_file) + '.*', [abs_file])) elif dswith('.c') or dswith('.pyc'):
continue
else:
copy_file(abs_file, os.path.join(build_root_dir, abs_file))
if dswith('__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()
setup(
name='my_project',
version='1.0',
ext_modules=cythonize(
extensions,
nthreads=16,
compiler_directives=dict(always_allow_keywords=True),
include_path=[_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
import t
if __name__ == '__main__':
hello()
fun1()
t()
$ python main.py
this is in hello
this is in fun1
this is in t

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。