使用Cython将Python打包成wheel文件 - AruiLR/MyNote GitHub Wiki

Wheel文件

Wheel是Python的一种打包格式(.whl),目的是支持不需要编译的安装过程并且能够起到保护Python源码的作用。它实际上也是一种压缩文件,将.whl的后缀改为.zip即可可看到压缩包里面的内容。Wheel现在被认为是Python的二进制包的标准格式。

打包过程

依赖:

  1. distutils
  2. setuptools
  3. Cython
  4. wheel

以上是在打包过程中需要用到的工具,可通过pip快速安装。

目录结构

目录结构

将需要打包的Python工程按照上述目录结构组织,其中root为打包后的Module,__init__.py是实现import所必须的。接下来编写setup.py进行打包即可。

setup.py

Linux

from setuptools import setup
from setuptools.extension import Extension
from Cython.Build import cythonize
from Cython.Distutils import build_ext

setup(
    name="MyModule"  # 模块名称 import MyModule,
    ext_modules=cythonize(
        [
           Extension("pkg1.*", ["root/pkg1/*.py"]),
           Extension("pkg2.*", ["root/pkg2/*.py"]),
           Extension("1.*", ["root/*.py"])
        ],
        build_dir="build",
        compiler_directives=dict(
        always_allow_keywords=True
        )),
    cmdclass=dict(
        build_ext=build_ext
    ),
    packages=["pkg1", "pkg2"]  # packages=[]时打包后的wheel文件中不含源码(.py)
)

python setup.py bdist_wheel

Windows

from setuptools import setup
from setuptools.extension import Extension
from Cython.Distutils import build_ext
from Cython.Build import cythonize
from pathlib import Path
import shutil


class MyBuildExt(build_ext):
    def run(self):
        build_ext.run(self)

        build_dir = Path(self.build_lib)
        root_dir = Path(__file__).parent
        target_dir = build_dir if not self.inplace else root_dir

        self.copy_file(Path('root/pkg1') / '__init__.py', root_dir, target_dir)
        self.copy_file(Path('root/pkg2') / '__init__.py', root_dir, target_dir)
        self.copy_file(Path('root') / '__init__.py', root_dir, target_dir)
    def copy_file(self, path, source_dir, destination_dir):
        if not (source_dir / path).exists():
            return
        shutil.copyfile(str(source_dir / path), str(destination_dir / path))

setup(
    name="MyModule",
    ext_modules=cythonize(
        [
           Extension("pkg1.*", ["root/pkg1/*.py"]),
           Extension("pkg2.*", ["root/pkg2/*.py"]),
           Extension("1.*", ["root/*.py"])
        ],
        build_dir="build",
        compiler_directives=dict(
        always_allow_keywords=True
        )),
    cmdclass=dict(
        build_ext=MyBuildExt
    ),
    packages=[],
)

**注意:**在Windows下打包时由于系统原因无法编译__init__.py,要解决这个问题,我们在构建项目的其余部分后从源码树中复制__init__.py文件,通过覆盖setup.py中的build_ext类。

安装

pip install *.whl