python --打包pyd或so文件

编译pyd或so

utils_ccc.py 文件(核心)

python 复制代码
# coding: utf-8
import argparse

from setuptools import setup, Extension
from Cython.Build import cythonize  # python 3.10.3  cython==3.1.6
from pathlib import Path
from loguru import logger
import shutil
import sys

BASE_DIR = Path(__file__).resolve().parent  # 项目根路径
PYD_PATH = BASE_DIR.joinpath('output_pyd')  # pyd输出文件
TEMP_PATH = BASE_DIR.joinpath('temp')  # 临时文件



class CompileTools(object):
    '''编译'''

    def __init__(self, py_file: str):
        self.py_file: Path = Path(py_file)  # 待编译文件
        self._module_name = self.py_file.stem  # 文件名(无后缀)
        self.__C_PATH = BASE_DIR.joinpath(f'{self._module_name}.c')  # c文件绝对路径
        self._system_win: int = 1 if sys.platform.startswith("win") else 0 # 1为win 0为linux


    def unlink_file(self):
        '''重置目录'''
        shutil.rmtree(PYD_PATH) # 清空pyd文件夹
        logger.debug(f'清空:[{PYD_PATH}]')


    def _get_ext_compile_args_and_compile_args(self) -> tuple:
        '''根据系统生成编译参数'''
        if self._system_win == 1: # windows
            ext_compile_args = [
                "/std:c99",  # 指定 C99 标准
                "/O2",  # 优化编译(对应 GCC 的 -O2)
                "/LD"  # 生成动态库(对应 GCC 的 -shared)
            ]
            ext_link_args = []
        else:
            ext_compile_args = [
                "-std=c99",  # 指定 C99 标准
                "-O2",  # 优化编译
                "-fPIC"  # 位置无关代码(Linux 必需)
            ]
            ext_link_args = ["-shared"]  # 生成动态库
        return ext_compile_args, ext_link_args


    def start(self):
        '''编译'''
        PYD_PATH.mkdir(parents=True, exist_ok=True)  # 创建文件夹
        TEMP_PATH.mkdir(parents=True, exist_ok=True)  # 创建文件夹

        ext_compile_args, ext_link_args = self._get_ext_compile_args_and_compile_args()
        ext = Extension(  # 定义扩展模块
            name=self._module_name,  # 定义编译后扩展模块的 import 名称(核心,决定你怎么导入)
            sources=[str(self.py_file), ],  # 仅作为「扩展模块的元信息标识」(类似标签),不影响 import、不影响编译结果
            extra_compile_args=ext_compile_args,  # 强制编译器用  C99(新标准):支持在 for 循环里直接声明变量(这是现在写 C 代码的常规写法)
            extra_link_args=ext_link_args # 链接参数(Linux 需加 -shared)
        )

        setup(  # 执行编译
            name=self._module_name,
            ext_modules=cythonize(ext, compiler_directives={
                "language_level": "3",  # 强制 Python3 语法
                "always_allow_keywords": True,  # 兼容关键字参数
            }),
            script_args=["build_ext",  # 编译扩展模块(固定参数)
                         # "--inplace",                        # 输出到当前目录(和底部参数互斥)
                         "--build-lib", f"{PYD_PATH}",  # 指定pyd输出路径
                         '--build-temp', f"{TEMP_PATH}"]  # 指定编译过程中产生的临时文件路径
        )

        pyd_abs_path = Path(self._find_pyd()) # 编译后文件pyd文件的绝对路径
        logger.success(f'编译成功:[{pyd_abs_path}]')

        if self._system_win == 1:
            new_name = PYD_PATH.joinpath(f'{self._module_name}.pyd')
        else:
            new_name = PYD_PATH.joinpath(f'{self._module_name}.so')

        pyd_abs_path.rename(new_name)
        logger.warning(f'重命名[{pyd_abs_path}]-->[{new_name}]')

        self.__C_PATH.unlink() # 删除.c扩展
        logger.debug(f'删除:[{self.__C_PATH}]')

        shutil.rmtree(TEMP_PATH) # 清空临时文件夹
        logger.debug(f'清空:[{TEMP_PATH}]')
        return new_name

    def _find_pyd(self):
        '''查找编译后文件'''
        if self._system_win == 1:
            pyd_path = next(PYD_PATH.glob('*.pyd'))
        else:
            pyd_path = next(PYD_PATH.glob('*.so'))
        return BASE_DIR.joinpath(pyd_path) # 固定只编译一个 所以只有一个文件


parser = argparse.ArgumentParser()
parser.add_argument('--py', type=str)
args = parser.parse_args()
c = CompileTools(args.py)
c.start()

server_ccc.py

python 复制代码
# coding: utf-8
import shutil
import time
from pathlib import Path
import sys
from fastapi import FastAPI, Request, UploadFile, File
from starlette.middleware.cors import CORSMiddleware
from starlette.responses import FileResponse
from loguru import logger
import uvicorn
import subprocess

BASE_DIR = Path(__file__).resolve().parent  # 项目根路径
PYD_PATH = BASE_DIR.joinpath('output_pyd').resolve()  # pyd输出文件
UTILS_PATH = BASE_DIR.joinpath('utils_ccc.py').resolve() # utils路径
PYTHON_PATH = r'D:\code\ceshi\.venv\Scripts\python.exe' # python 解释器路径

app = FastAPI()
app.add_middleware(  # 跨域
    CORSMiddleware,
    allow_origins=["*", ],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

@app.post("/compile")
async def compile(file: UploadFile = File(...)):
    '''加密文件'''
    file_path = BASE_DIR.joinpath(f'{file.filename}').resolve()
    with open(str(file_path), 'wb') as f:
        for i in iter(lambda: file.file.read(512), b''):
            f.write(i)
    logger.warning(f'文件保存地址:【{file_path}】')

    s = subprocess.Popen([
        PYTHON_PATH,
        f'{UTILS_PATH}',
        '--py',
        f'{file_path}'
    ])
    s.wait() # 等待执行结束

    if sys.platform.startswith('win'):
        pyd_path = next(PYD_PATH.glob('*.pyd')).resolve()
    else:
        pyd_path = next(PYD_PATH.glob('*.so')).resolve()

    logger.success(f'返回文件:[{pyd_path}]')
    file_path.unlink() # 删除原上传文件
    logger.debug(f'删除文件:[{file_path}]')
    return FileResponse(
        path=pyd_path,
        filename=pyd_path.name,  # 下载时显示的文件名(可选)
        media_type='application/octet-stream',
    )

@app.get('/del_pyd')
async def del_pyd(request: Request):
    '''删除pyd文件'''
    shutil.rmtree(PYD_PATH)
    logger.error(f'删除:【{PYD_PATH}】')
    return {'code': 0, 'msg': 'success'}


if __name__ == "__main__":
    uvicorn.run(app, host='0.0.0.0', port=8009, workers=1)

客户端

python 复制代码
# coding: utf-8
import sys
from pathlib import Path
from loguru import logger
import requests


class Compile(object):
    '''编译'''

    def __init__(self):
        self.url = 'http://x.x.x.x:8000' # 服务器ip
        self._desk_top = Path.home().joinpath("Desktop")
        self._system_win: int = 0  # 1为win 0为linux   取决于服务端是在那个平台  win固定为pyd linux固定为so


    def save_file(self, response, name):
        '''保存文件'''
        if self._system_win:
            file_abs_path = self._desk_top.joinpath(f'{name}.pyd')
        else:
            file_abs_path = self._desk_top.joinpath(f'{name}.so')

        with open(file_abs_path, 'wb') as f:
            f.write(response)
        logger.success(f'编译成功:【{file_abs_path}】')

    def _del_pyd(self):
        '''删除服务器的pyd文件'''
        res = requests.get(f"{self.url}/del_pyd").json()
        logger.success(f'清空pyd,【{res}】')

    def start(self, file_path):
        '''开始编译'''
        logger.debug(f'开始云编译;【{file_path}】')
        filename = Path(file_path).name
        with open(file_path, 'rb') as f:
            file = f.read()

        response = requests.post(f'{self.url}/compile', files={"file": (filename, file)}).content
        name = Path(file_path).stem
        self.save_file(response, name)
        self._del_pyd()


if __name__ == '__main__':
    a = Compile()
    a.start(r'D:\code\ceshi\aaa.py')

编译pyc

server.py

python 复制代码
# coding=utf-8
# @Time : 2025/7/9 8:34
# @Author : XiaoYi
# @Email: 1206154726@qq.com
# @Filename: server
import compileall
from loguru import logger
from fastapi import FastAPI, Request, UploadFile, File
from starlette.middleware.cors import CORSMiddleware
from pathlib import Path
import uvicorn

from starlette.responses import FileResponse

app = FastAPI()
BASE_DIR = Path(__file__).parent

CACHE_PY = BASE_DIR.joinpath('cache.py')  # 暂存py文件
CACHE_PYC = BASE_DIR.joinpath('cache.pyc') # 暂存的pyc文件

app.add_middleware(  # 跨域
    CORSMiddleware,
    allow_origins=["*", ],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)


def pack_pyc_file():
    '''编译单个文件'''
    compileall.compile_file(
        fullname=CACHE_PY,  # 必选:单个文件的完整路径
        force=True,  # 强制重新编译
        optimize=2,  # 最高优化级别
        quiet=0,
        legacy=True
    )
    logger.debug(f'编译pyc【{CACHE_PY}】成功!')

@app.post("/compile")
async def compile(file: UploadFile = File(...)):
    '''加密文件'''
    logger.warning(f'图片保存地址:【{CACHE_PY}】')

    with open(str(CACHE_PY), 'wb') as f:
        for i in iter(lambda: file.file.read(512), b''):
            f.write(i)

    pack_pyc_file()
    return FileResponse(
        path=CACHE_PYC,
        filename=CACHE_PYC.name,  # 下载时显示的文件名(可选)
        media_type='application/octet-stream',
    )


if __name__ == '__main__':
    uvicorn.run(app, host='127.0.0.1', port=8001, workers=1)

客户端

python 复制代码
from pathlib import Path
from loguru import logger
import requests


class Compile(object):
    '''编译'''

    def __init__(self):
        self.url = 'http://xx.xx.xx.xx:800/compile'
        self._desk_top = Path.home().joinpath("Desktop")


    def save_file(self, response, name):
        '''保存文件'''
        file_abs_path = self._desk_top.joinpath(f'{name}.pyc')
        with open(file_abs_path, 'wb') as f:
            f.write(response)
        logger.success(f'编译成功:【{file_abs_path}】')

    def start(self, file_path):
        '''开始编译'''
        logger.debug(f'开始云编译;【{file_path}】')
        filename = Path(file_path).name
        with open(file_path, 'rb') as f:
            file = f.read()

        response = requests.post(self.url, files={"file": (filename, file)}).content
        name = Path(file_path).stem
        self.save_file(response, name)

if __name__ == '__main__':
    a = Compile()
    a.start(r'D:\code\huanqinglvyou\backstage\models.py')
相关推荐
Helen_cai11 小时前
OpenHarmony 完整项目工程整合规范 + 模块化分层架构(API23+ 标准企业级结构)
开发语言·华为·php·harmonyos
大圣编程15 小时前
Java 多维数组详解
java·开发语言
殳翰18 小时前
下服务器端开发流程及相关工具介绍(C++)
开发语言·c++
落寞的星星19 小时前
这个对象就包含了已经转换好的DFA和各种词法分析器运转所需要的参数。下一步,我们就可以用ScannerInfo对象创建出Scanner对象,请看下面的代码:
开发语言·c#
nothing&nowhere19 小时前
用 Python 做问卷数据清洗:无效样本检测与处理实战
开发语言·python·数据清洗·数据处理·问卷星·问卷星脚本·刷问卷
花酒锄作田19 小时前
如何发布自己的 Python 库到 PyPI
python
2601_9615934219 小时前
Rust 开发环境配置繁琐?RustRover 开箱即用搞定编码调试
开发语言·后端·macos·rust
researcher-Jiang19 小时前
Design Patterns——Template Method入门到情景实战
python·设计模式·模板方法模式
HZZD_HZZD20 小时前
DL/T 645-2026新国标深度解读与智能电表协议适配实战:从帧结构变化到Java采集器升级的全链路改造方案
java·开发语言