使用distutils分发预编译的python扩展模块

19 投票
5 回答
11219 浏览
提问于 2025-04-15 20:26

今天来聊一个简单的问题:我正在学习Python的distutils库,想把一个Python扩展模块(.pyd文件)包含在我的包里。我知道推荐的做法是让distutils在创建包的时候编译这个扩展,但这个扩展比较复杂,涉及很多源文件,还引用了几个外部库,所以要让它一切正常运行需要花不少时间。

与此同时,我手头有一个在Visual Studio中成功构建的扩展,想在安装程序中临时使用它,这样我就可以把精力放在其他问题上。不过,我不能把它指定为模块,因为模块似乎必须有明确的.py扩展名。那么,我该如何在我的setup.py文件中指明要包含一个预编译的扩展模块呢?

(Python 3.1,如果这很重要的话)

5 个回答

1

我在用Python 3.7、CMake 3.15.3和Swig 4.0.1在Visual Studio 2017中构建一个扩展库时,遇到了同样的问题。构建系统会生成三个文件:mymodule.py、_mymodule.lib和_mymodule.pyd。经过一番尝试和错误,我发现以下组合可以成功:

  1. 首先,创建一个'setup.cfg'文件,说明你要安装一个包;例如:
    [metadata]
    name = mymodule
    version = 1.0

    [options]
    include_package_data = True
    package_dir=
        =src
    packages=mymodule
    python_requires '>=3.7'

    [options.package_data]
    * = *.pyd
  1. 然后,修改CMake,让它生成一个特定的目录结构,如下所示:
    setup.py
    setup.cfg
    src/
        mymodule/
                 __init__.py
                 _mymodule.pyd
  1. 接下来,'setup.py'文件就很简单了:
    setup()

这需要CMake将'mymodule.py'输出文件重命名为init.py。我是通过CMake中的'install'命令来实现的:


    install (TARGETS ${SWIG_MODULE_${PROJECT_NAME}_REAL_NAME} DESTINATION "${CMAKE_BINARY_DIR}/dist/src/${PROJECT_NAME}")
    install (FILES 
            setup.py
            setup.cfg
            DESTINATION "${CMAKE_BINARY_DIR}/dist"
    )

    install (FILES
            ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.py
            DESTINATION "${CMAKE_BINARY_DIR}/dist/src/${PROJECT_NAME}"
            RENAME "__init__.py"

我认为,让这个过程成功的关键在于将构建输出重新组织成一个Python包,而不是让setup使用默认的构建输出作为Python脚本。

8

我通过重写 Extension.build_extension 方法来解决这个问题:

setup_args = { ... }
if platform.system() == 'Windows':
    class my_build_ext(build_ext):
        def build_extension(self, ext):
            ''' Copies the already-compiled pyd
            '''
            import shutil
            import os.path
            try:
                os.makedirs(os.path.dirname(self.get_ext_fullpath(ext.name)))
            except WindowsError, e:
                if e.winerror != 183: # already exists
                    raise


            shutil.copyfile(os.path.join(this_dir, r'..\..\bin\Python%d%d\my.pyd' % sys.version_info[0:2]), self.get_ext_fullpath(ext.name))

    setup_args['cmdclass'] = {'build_ext': my_build_ext }

setup(**setup_args)

撰写回答