python把c扩展模块打包成免编译且可pip install包的方法

python用c语言编写的扩展模块,在setup.py sdist或者bdist打包时,默认是把c源码编译进去,在pip install的时候先编译再安装的,即使我把so加到了MANIFEST.in文件中,查看编出来的包里包含了so,但是仍然不会进行so的安装。搜索查阅了一下,没有找到相关的文章可以解决这个问题。因此只能自己通过debug研究python-prctl的安装流程,把相关的流程走通了。总结记录一下:

背景

首先说明一下背景:

我们的业务逻辑有一部分需要使用c语言来写,这部分c语言写的代码只能在特定的编译机上编译成.so,我们使用的python版本统一定为python3.8。因此我编写了一个python的c扩展模块,并对底层c接口进行了相应的python函数包装,在使用的时候,需要放到内网pip源,用pip install的方式安装。所有使用人的os版本、python版本都是一致的,且没有编译需要的相关文件。

原理

调试方式

由于时间比较紧,所以没有详细研究pip install的流程,python-prctl这个模块的项目结构和我们的类似,不同的是它是pip install的时候先编译再安装的,因此用它当作例子来学习。

在pycharm上配置ssh远程解释器,然后配置执行方式:

一些简单观察到的流程(没有实际看完整流程,只是靠debug和猜测推断了下)

pip install的流程

css 复制代码
1. egg_info
2. install
    1) build
        a) build_py
        b) build_clib
        c) build_ext
        d) build_scripts
    2) install_lib
    3) install_egg_info
    3) install_scripts

编译的.so,会拷贝到build/lib.linux-x86_64-3.8目录

实现思路

经过多次踩坑(一直不安装so,甚至改install命令自己把so拷贝到site-packages,但是uninstall时又不会卸载等等),最终确定一个实现的思路,虽然不编译,但是在build_ext的流程中,把对应的so拷贝到build/lib.linux-x86_64-3.8目录,让后续的安装流程能够正常安装进行,这样后面的流程能够正常的把so拷贝到site-packages目录,且卸载时正常删除。这样对原流程的改动最小,影响也最少。

build_ext命令的定制

我们的业务工程代码,对于Makefile有整体的封装,怎么搜索头文件,怎么搜索lib库都有封装,我们想要调用这些封装的内容来去找我们的依赖模块,这样我们依赖的模块有修改的话,该模块不用修改。所以不能在setup.py中写死include目录及libraries。因此对build_ext做一些定制。把include目录、链接的.a文件、源码通过Makefile传递进去。

setup.py 复制代码
class my_build_ext(build_ext):
    user_options = build_ext.user_options
    user_options.extend([
        ('extra-objects=', None, "list of extra_objects"),
        ('sources=', None, "list of sources")
    ])

    def initialize_options(self):
        super(my_build_ext, self).initialize_options()
        self.extra_objects = None
        self.sources = None

    def run(self):
        include_dirs = []
        for include_dir in self.include_dirs:
            include_dirs.extend(include_dir.split())
        self.include_dirs = include_dirs

        for extension in self.extensions:
            if self.sources:
                extension.sources = self.sources.split()
                if self.extra_objects:
                    extension.extra_objects = self.extra_objects.split()
        super(my_build_ext, self).run()

setup.py的定制

根据实现思路的描述,这里主要处理pip install的时候的流程,跳过编译阶段,直接把so拷贝到build目录

ruby 复制代码
def build_extensions(self):
    if not self.sources:
        # 没有传sources 说明是pip install, 跳过编译阶段, 直接把包里面的so拷贝到build目录
        for extension in self.extensions:
            # 找编译的目标目录
            ext_path = self.get_ext_fullpath(extension.name)
            ext_name = os.path.basename(ext_path)
            ext_dir = os.path.dirname(ext_path)
            mkpath(ext_dir, 0o777, dry_run=0)
            copy_file(ext_name, ext_dir)
        return
    # build
    super(my_build_ext, self).build_extensions()

makefile编写

在定义make all和make clean

make all时,先调用setup.py build_ext把so编译出来,然后拷贝到当前目录,最后调用setup.py sdist编译为可pip安装的包

makefile 复制代码
all:
   python3.8 setup.py build_ext --include-dirs="include" --sources="_sample_extension_mod.c"
   \cp build/lib.linux-x86_64-3.8/_sample_extension_mod.cpython-38-x86_64-linux-gnu.so .
   python3.8 setup.py sdist
clean:
   rm -rf build dist *.so

最终效果

可以看到最终实现了预期, 安装的时候, 自动把so给安装上了. 卸载的时候, 也顺利把so及相关文件卸载了.

相关推荐
喵手29 分钟前
Python爬虫实战:数据治理实战 - 基于规则与模糊匹配的店铺/公司名实体消歧(附CSV导出 + SQLite持久化存储)!
爬虫·python·数据治理·爬虫实战·零基础python爬虫教学·规则与模糊匹配·店铺公司名实体消岐
喵手30 分钟前
Python爬虫实战:国际电影节入围名单采集与智能分析系统:从数据抓取到获奖预测(附 CSV 导出)!
爬虫·python·爬虫实战·零基础python爬虫教学·采集数据csv导出·采集国际电影节入围名单·从数据抓取到获奖预测
派葛穆1 小时前
Python-PyQt5 安装与配置教程
开发语言·python·qt
自可乐1 小时前
Milvus向量数据库/RAG基础设施学习教程
数据库·人工智能·python·milvus
可触的未来,发芽的智生1 小时前
发现:认知的普适节律 发现思维的8次迭代量子
javascript·python·神经网络·程序人生·自然语言处理
sheji34161 小时前
【开题答辩全过程】以 基于SpringBoot的疗养院管理系统的设计与实现为例,包含答辩的问题和答案
java·spring boot·后端
短剑重铸之日2 小时前
《设计模式》第六篇:装饰器模式
java·后端·设计模式·装饰器模式
真智AI2 小时前
用 LLM 辅助生成可跑的 Python 单元测试:pytest + coverage 覆盖率报告(含运行指令与排坑)
python·单元测试·pytest
0思必得02 小时前
[Web自动化] Selenium处理文件上传和下载
前端·爬虫·python·selenium·自动化·web自动化
Hui Baby3 小时前
Java SPI 与 Spring SPI
java·python·spring