Python 項目轉.so動態庫

最近, 做者遇到一個需求, 須要把Python的工程部署到別的集羣, 可是又要保證Python代碼的安全性. 因而上網搜索, 搜到幾個解決方案, 可是都不是符合需求. 綜合搜到的幾個解決方案, 最終做者採用了編譯成so動態庫的方式發佈.html

<!--more-->python

首先說一下搜到到幾個解決方案, 以及它們的優缺點linux

  1. 編譯成pyc發佈 優勢: 操做簡單 缺點: 能夠被反編譯
  2. cx_freeze 優勢: 能夠經過freeze命令直接把一個項目全部的依賴生成一個二進制, 因此部署到新的環境時, 十分方便 缺點: freeze命令若是工程項目很大的話, 速度很是慢, 並且其生成的Python代碼其實也是pyc, 能夠被反編譯
  3. pyminifier 優勢: 經過代碼混淆的方式保護代碼的安全 缺點: 貌似, 只對單個文件的混淆其做用, 若是是一個工程項目就很差使了
  4. 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

未經容許禁止轉載 https://spxcds.com/2018/12/05/python_to_so

相關文章
相關標籤/搜索