前提条件:

  • 确保电脑已经安装gcc且终端能检索到
  • 确保Python中已经安装cython包,若未安装,则先使用pip install cython进行安装

打包方法:

  • step1:编写编译脚本setup.py,代码如下:
# encoding = utf-8
from distutils.core import setup
from Cython.Build import cythonize
setup(
name = 'libname',
ext_modules = cythonize(["file1.py", "file2.py", 	"dir/file3.py"], language_level = "3")
)
  • step2:终端中执行:
python3 setup.py build_ext

需要注意的点:

  1. setup()中的ext_modules不是exe_modules,千万不要打错了,如果打错了,你会发现只编译成了.c文件,而没用生成.so
  2. 生成的.so只能在打包计算机相同系统、相同平台及相同python版本环境下使用,要想在其他平台或环境下使用,需要在指定平台或环境下重新打包
  3. 存在多个python版本时,如在linux下博主python2和python3共存,需要指定编译用的python版本,否则终端会报警告FutureWarning: Cython directive ‘language_level’ not set, using 2 for now (Py2)。指定版本有两种方式:
  • method 1: 在每个要打包的py文件头部写入# cython:language_level=3
  • method 2: 像上述setup.py中一样,在cythonize函数中加入language_level = "3"选项

    4.打包完的.so文件结构会被打乱,如上述dir中的file3.py生成的file3.so与其他两个同目录,而不是在dir文件夹下,如果不做调整,执行脚本时可能存在import错误,因此还要手动调整一下文件结构

附批量打包的代码:

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

# code from:   https://blog.csdn.net/qq_33375598/article/details/118677130

# 构建后存放的文件目录
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) # 返回path最后的文件名
    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
    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'): # 用空白的__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='your_project_name',
    version='1.0',
    ext_modules=cythonize(
        extensions,
        nthreads=16,
        compiler_directives=dict(always_allow_keywords=True),
        include_path=[numpy.get_include()], language_level="3"),
    cmdclass=dict(build_ext=build_ext))

参考链接:

  • https://blog.csdn.net/qq_33375598/article/details/118677130
  • ht
  • tps://blog.csdn.net/weixin_36755535/article/details/127300870