乐于分享
好东西不私藏

Python打包快速上手:从脚本到软件

Python打包快速上手:从脚本到软件

目录

  1. 核心概念与决策
  2. 第一部分:构建可分发的库包
    • 标准化的项目结构
    • 现代项目配置核心:pyproject.toml
    • 传统工具:setuptools
    • 实战案例:命令行计算器
  3. 第二部分:现代包管理工具详解
    • Poetry:新一代包管理利器
    • PDM:标准驱动型包管理器
    • Poetry vs PDM 对比与选择
    • uv:极速包管理器(2025 年首选)
    • Hatch:PyPA 官方项目管理工具
  4. 第三部分:生成可独立运行的应用程序
    • PyInstaller:最通用的应用打包器
    • Nuitka:追求性能的编译器
    • 跨平台打包指南
    • 实战案例:GUI应用打包
  5. 第四部分:Docker 容器化部署
  6. 第五部分:Conda 打包
  7. 第六部分:CI/CD 自动化发布
  8. 常见问题
  9. 总结

1. 概念与选型

Python 打包是将你的代码及其所有依赖项,整合成可在目标环境中轻松运行的分发格式的过程。

Python程序打包的两大流派:

几种打包格式对比

格式
类型
目标用户
是否需要 Python
典型工具
sdist
 (.tar.gz)
源码包
开发者
setuptools, Poetry, PDM
wheel
 (.whl)
二进制包
开发者
setuptools, Poetry, PDM
可执行文件
 (.exe/.app)
独立应用
最终用户
PyInstaller, Nuitka
Docker 镜像
容器
运维/开发者
包含在内
Docker
Conda 包
 (.tar.bz2)
跨语言包
数据科学家
是(Conda环境)
conda-build

选型流程

这个流程图将贯穿全文,帮助你在不同场景下做出正确的选择:


1. 构建可分发的库包

1.1 标准化的项目结构

一个清晰的项目结构是所有事情的开端。推荐使用 src 布局,它能避免在开发过程中意外导入未安装的包。

my_awesome_project/├── src/                            # 源代码根目录│   └── mypackage/                  # 你的包名│       ├── __init__.py             # 包的标志文件│       ├── core.py                 # 核心逻辑│       ├── utils.py                # 工具函数│       ├── cli.py                  # 命令行接口│       └── data/                   # 包内数据文件│           └── config.json├── tests/                          # 测试代码│   ├── __init__.py│   ├── test_core.py│   ├── test_utils.py│   └── conftest.py├── docs/                           # 文档│   ├── index.md│   └── api.md├── .github/                        # GitHub 配置│   └── workflows/│       ├── ci.yml│       └── release.yml├── pyproject.toml                  # 【核心】现代项目配置├── README.md├── LICENSE└── .gitignore

为什么使用 src 布局?

# 没有 src 布局时,可能会意外导入本地代码而不是已安装的包import mypackage  # 这导入的是当前目录,而不是 site-packages 中的版本# 使用 src 布局后,必须 pip install -e . 才能导入# 这确保了开发环境和用户安装环境的行为一致

1.2 现代项目配置核心:pyproject.toml

pyproject.toml 是所有现代 Python 打包工具的主要配置文件。它取代了传统的 setup.pysetup.cfgrequirements.txt 等多个文件。

完整配置示例

[build-system]requires = ["setuptools>=61.0", "wheel"]build-backend = "setuptools.build_meta"[project]name = "my-awesome-package"version = "0.1.0"description = "一个演示用的 Python 包"authors = [    {name = "Your Name", email = "you@example.com"}]maintainers = [    {name = "Maintainer Name", email = "maintainer@example.com"}]license = "MIT"readme = "README.md"requires-python = ">=3.8"keywords = ["example", "packaging", "tutorial"]classifiers = [    "Development Status :: 3 - Alpha",    "Intended Audience :: Developers",    "License :: OSI Approved :: MIT License",    "Programming Language :: Python :: 3",    "Programming Language :: Python :: 3.8",    "Programming Language :: Python :: 3.9",    "Programming Language :: Python :: 3.10",    "Programming Language :: Python :: 3.11",    "Programming Language :: Python :: 3.12",    "Programming Language :: Python :: 3.13",]# 核心依赖dependencies = [    "requests>=2.28.0",    "click>=8.0.0,<9.0.0",    "pydantic>=2.0.0",]# 可选依赖(用户可以用 pip install mypackage[gui] 安装)[project.optional-dependencies]dev = [    "pytest>=7.0.0",    "pytest-cov>=4.0.0",    "black>=23.0.0",    "ruff>=0.1.0",    "mypy>=1.0.0",]gui = [    "PyQt5>=5.15.0",    "matplotlib>=3.5.0",]all = [    "mypackage[dev,gui]",]# 命令行入口点[project.scripts]mycli = "mypackage.cli:main"# GUI 入口点[project.gui-scripts]mygui = "mypackage.gui:main"# 项目链接[project.urls]Homepage = "https://github.com/yourname/my-awesome-package"Documentation = "https://my-awesome-package.readthedocs.io"Repository = "https://github.com/yourname/my-awesome-package.git"Issues = "https://github.com/yourname/my-awesome-package/issues"# ========== 以下是 setuptools 特有配置 ==========[tool.setuptools]package-dir = {"" = "src"}packages = {find = {where = ["src"]}}include-package-data = true[tool.setuptools.package-data]mypackage = [    "data/*.json",    "data/*.yaml",    "templates/*.html",][tool.setuptools.exclude-package-data]mypackage = ["*.pyc", "__pycache__/*"]# ========== 工具配置 ==========[tool.black]line-length = 100target-version = ['py38', 'py39', 'py310', 'py311', 'py312', 'py313'][tool.ruff]line-length = 100select = ["E", "F", "I", "N", "W"][tool.mypy]python_version = "3.8"warn_return_any = truewarn_unused_configs = truestrict = true[tool.pytest.ini_options]testpaths = ["tests"]python_files = ["test_*.py"]python_classes = ["Test*"]python_functions = ["test_*"]addopts = "-v --cov=mypackage --cov-report=html"

1.3 传统工具:setuptools

setuptools 是 Python 打包的基石。即使使用 Poetry 或 PDM,它们底层也依赖打包标准(PEP 517/518),而 setuptools 是最经典的构建后端。

构建与发布流程

# 1. 安装构建和发布工具pip install --upgrade build twine# 2. 清理旧的构建产物rm -rf dist/ build/ *.egg-info# 3. 构建包(在项目根目录执行)python -m build# 这会在 dist/ 目录生成两个文件:#   - mypackage-0.1.0.tar.gz   (sdist - 源码包)#   - mypackage-0.1.0-py3-none-any.whl  (wheel - 二进制包)# 4. 检查构建产物是否符合 PyPI 规范python -m twine check dist/*# 5. 上传到 TestPyPI 进行测试python -m twine upload --repository testpypi dist/*# 6. 从 TestPyPI 安装测试pip install --index-url https://test.pypi.org/simple/ my-package# 7. 测试通过后,上传到正式 PyPIpython -m twine upload dist/*

可编辑模式开发

# 在项目根目录以开发模式安装pip install -e .# 带可选依赖pip install -e ".[dev]"# 这样对源码的任何修改都会立即生效,无需重新安装# 这是日常开发中最常用的安装方式

版本管理策略

# 推荐使用语义化版本(Semantic Versioning)# 格式:MAJOR.MINOR.PATCH# 示例版本演进:# 0.1.0  -> 初始开发版本# 0.2.0  -> 新增功能,向后兼容# 0.2.1  -> 修复 bug,向后兼容# 1.0.0  -> 第一个稳定版本# 1.1.0  -> 新增功能,向后兼容# 2.0.0  -> 不兼容的 API 更改# 在 pyproject.toml 中管理版本[project]version = "1.0.0"# 或者使用动态版本(从 VCS 标签读取)# 需要安装 setuptools-scm[build-system]requires = ["setuptools>=61.0""setuptools-scm>=8.0"][tool.setuptools_scm]write_to = "src/mypackage/_version.py"

1.4 案例:命令行计算器

让我们通过一个"科学计算器"项目来实践打包流程。

项目结构

calculator/├── src/│   └── calculator/│       ├── __init__.py│       ├── core.py│       └── cli.py├── tests/│   ├── __init__.py│   ├── test_core.py│   └── conftest.py├── pyproject.toml├── README.md└── LICENSE

src/calculator/__init__.py - 包的公开接口

"""科学计算器包提供基本的数学运算和命令行接口。"""from .core import Calculator__all__ = ["Calculator"]__version__ = "0.1.0"

src/calculator/core.py - 核心功能

"""计算器核心功能模块."""import mathfrom typing import Union, List, OptionalNumber = Union[int, float]classCalculator:"""科学计算器类.    Examples:        >>> calc = Calculator()        >>> calc.add(10, 20)        30        >>> calc.sqrt(16)        4.0        >>> calc.get_history()        ['10 + 20 = 30', '√16 = 4.0']    """def__init__(self) -> None:"""初始化计算器."""        self._history: List[str] = []defadd(self, a: Number, b: Number) -> Number:"""        加法运算.        Args:            a: 第一个加数            b: 第二个加数        Returns:            两数之和        """        result = a + b        self._record(f"{a} + {b} = {result}")return resultdefsubtract(self, a: Number, b: Number) -> Number:"""        减法运算.        Args:            a: 被减数            b: 减数        Returns:            差        """        result = a - b        self._record(f"{a} - {b} = {result}")return resultdefmultiply(self, a: Number, b: Number) -> Number:"""        乘法运算.        Args:            a: 第一个乘数            b: 第二个乘数        Returns:            积        """        result = a * b        self._record(f"{a} × {b} = {result}")return resultdefdivide(self, a: Number, b: Number) -> float:"""        除法运算.        Args:            a: 被除数            b: 除数        Returns:            商        Raises:            ZeroDivisionError: 当除数为零时        """if b == 0:raise ZeroDivisionError("除数不能为零")        result = a / b        self._record(f"{a} ÷ {b} = {result}")return resultdefpower(self, base: Number, exponent: Number) -> float:"""        幂运算.        Args:            base: 底数            exponent: 指数        Returns:            幂结果        """        result = math.pow(base, exponent)        self._record(f"{base} ^ {exponent} = {result}")return resultdefsqrt(self, x: Number) -> float:"""        平方根计算.        Args:            x: 输入数字        Returns:            平方根        Raises:            ValueError: 当输入为负数时        """if x < 0:raise ValueError("不能计算负数的平方根")        result = math.sqrt(x)        self._record(f"√{x} = {result}")return resultdef_record(self, entry: str) -> None:"""记录计算历史(内部方法)."""        self._history.append(entry)# 只保留最近 100 条记录if len(self._history) > 100:            self._history.pop(0)defget_history(self) -> List[str]:"""        获取计算历史.        Returns:            历史记录列表        """return self._history.copy()defclear_history(self) -> None:"""清除计算历史."""        self._history.clear()

src/calculator/cli.py - 命令行接口

"""命令行接口模块."""import sysimport clickfrom .core import Calculator@click.group()@click.version_option(version="0.1.0", prog_name="calculator")defmain():"""科学计算器 - 强大的命令行计算工具."""pass@main.command()@click.argument('a', type=float)@click.argument('b', type=float)defadd(a, b):"""计算 a + b."""    calc = Calculator()    result = calc.add(a, b)    click.echo(f"结果: {result}")@main.command()@click.argument('a', type=float)@click.argument('b', type=float)defsubtract(a, b):"""计算 a - b."""    calc = Calculator()    result = calc.subtract(a, b)    click.echo(f"结果: {result}")@main.command()@click.argument('a', type=float)@click.argument('b', type=float)defmultiply(a, b):"""计算 a × b."""    calc = Calculator()    result = calc.multiply(a, b)    click.echo(f"结果: {result}")@main.command()@click.argument('a', type=float)@click.argument('b', type=float)defdivide(a, b):"""计算 a ÷ b."""    calc = Calculator()try:        result = calc.divide(a, b)        click.echo(f"结果: {result}")except ZeroDivisionError as e:        click.echo(f"错误: {e}", err=True)        sys.exit(1)@main.command()@click.argument('base', type=float)@click.argument('exponent', type=float)defpower(base, exponent):"""计算 base ^ exponent."""    calc = Calculator()    result = calc.power(base, exponent)    click.echo(f"结果: {result}")@main.command()@click.argument('x', type=float)defsqrt(x):"""计算 √x."""    calc = Calculator()try:        result = calc.sqrt(x)        click.echo(f"结果: {result}")except ValueError as e:        click.echo(f"错误: {e}", err=True)        sys.exit(1)@main.command()defhistory():"""显示计算历史."""    calc = Calculator()    history_list = calc.get_history()ifnot history_list:        click.echo("暂无历史记录")else:        click.echo("计算历史:")for i, record in enumerate(history_list, 1):            click.echo(f"  {i}{record}")if __name__ == '__main__':    main()

tests/test_core.py - 单元测试

"""计算器核心功能测试."""import pytestfrom calculator.core import CalculatorclassTestCalculator:"""计算器测试类."""    @pytest.fixturedefcalc(self):"""创建计算器实例."""return Calculator()deftest_add(self, calc):"""测试加法."""assert calc.add(1020) == 30assert calc.add(-55) == 0assert calc.add(0.10.2) == pytest.approx(0.3)deftest_subtract(self, calc):"""测试减法."""assert calc.subtract(105) == 5assert calc.subtract(05) == -5deftest_multiply(self, calc):"""测试乘法."""assert calc.multiply(34) == 12assert calc.multiply(-23) == -6deftest_divide(self, calc):"""测试除法."""assert calc.divide(102) == 5assert calc.divide(73) == pytest.approx(2.333, rel=1e-3)deftest_divide_by_zero(self, calc):"""测试除以零."""with pytest.raises(ZeroDivisionError, match="除数不能为零"):            calc.divide(100)deftest_power(self, calc):"""测试幂运算."""assert calc.power(23) == 8assert calc.power(40.5) == 2.0deftest_sqrt(self, calc):"""测试平方根."""assert calc.sqrt(16) == 4.0assert calc.sqrt(2) == pytest.approx(1.414, rel=1e-3)deftest_sqrt_negative(self, calc):"""测试负数平方根."""with pytest.raises(ValueError, match="不能计算负数的平方根"):            calc.sqrt(-1)deftest_history(self, calc):"""测试历史记录."""        calc.add(12)        calc.subtract(53)        history = calc.get_history()assert len(history) == 2assert"1 + 2 = 3"in history[0]assert"5 - 3 = 2"in history[1]deftest_clear_history(self, calc):"""测试清除历史."""        calc.add(12)        calc.clear_history()assert len(calc.get_history()) == 0

pyproject.toml - 项目配置

[build-system]requires = ["setuptools>=61.0"]build-backend = "setuptools.build_meta"[project]name = "calculator"version = "0.1.0"description = "一个命令行科学计算器"readme = "README.md"requires-python = ">=3.8"license = "MIT"authors = [    {name = "Your Name", email = "you@example.com"}]dependencies = [    "click>=8.0.0",][project.optional-dependencies]dev = [    "pytest>=7.0.0",    "pytest-cov>=4.0.0",    "black>=23.0.0",][project.scripts]calc = "calculator.cli:main"[project.urls]Homepage = "https://github.com/yourname/calculator"Repository = "https://github.com/yourname/calculator.git"[tool.setuptools]package-dir = {"" = "src"}packages = {find = {where = ["src"]}}

构建和使用

# 构建包cd calculatorpython -m build# 安装pip install dist/calculator-0.1.0-py3-none-any.whl# 使用 CLI$ calc add 10 20结果: 30$ calc divide 10 0错误: 除数不能为零$ calc sqrt -- -1错误: 不能计算负数的平方根# 注意:传递负数参数时需要使用 `--` 分隔符# 否则 click 会将 -1 误解析为命令行选项# 在 Python 中使用$ python -c "from calculator import Calculator; c=Calculator(); print(c.multiply(6, 7))"42

2. 现代包管理工具详解

2.1 Poetry:新一代包管理利器

Poetry 是目前最流行的现代 Python 包管理工具。它将依赖管理虚拟环境管理打包构建发布集成于一体。

传统方式
Poetry
pip
 + virtualenv + setup.py + requirements.txt
一条 poetry 命令
手动管理虚拟环境
自动创建和管理
requirements.txt
 版本可能不精确
poetry.lock
 锁定精确版本
多个配置文件
统一在 pyproject.toml

安装 Poetry

# Linux / macOS - 官方安装脚本curl -sSL https://install.python-poetry.org | python3 -# Windows (PowerShell)(Invoke-WebRequest -Uri https://install.python-poetry.org -UseBasicParsing).Content | python -# 使用 pip 安装(不推荐,可能导致依赖冲突)pip install poetry# 验证安装poetry --version# 启用 tab 补全(可选)# Bashpoetry completions bash >> ~/.bash_completion# Zshpoetry completions zsh > ~/.zfunc/_poetry# Fishpoetry completions fish > ~/.config/fish/completions/poetry.fish

项目初始化

# 创建新项目(会自动生成完整的项目结构)poetry new my-awesome-project# 生成的结构:# my-awesome-project/# ├── my_awesome_project/# │   └── __init__.py# ├── tests/# │   └── __init__.py# ├── pyproject.toml# └── README.md# 在已有项目中初始化(交互式向导)cd existing-projectpoetry init

依赖管理核心命令

# ========== 添加依赖 ==========# 添加生产依赖poetry add requests fastapi uvicorn# 添加指定版本poetry add "django>=4.2,<5.0"# 添加开发依赖poetry add --group dev pytest black mypy ruff# 添加多个分组poetry add --group test pytest-cov pytest-mockpoetry add --group docs sphinx# 从 Git 仓库安装poetry add git+https://github.com/org/repo.git# 从 Git 仓库指定分支poetry add git+https://github.com/org/repo.git#main# 从本地路径安装(开发模式)poetry add ../sibling-package --editable# ========== 移除依赖 ==========poetry remove requests# ========== 安装依赖 ==========# 安装所有依赖(包括 dev 组)poetry install# 仅安装生产依赖poetry install --only main# 安装指定组poetry install --with dev,docs# 不安装开发依赖poetry install --without dev# ========== 更新依赖 ==========# 更新所有依赖到最新兼容版本poetry update# 更新指定包poetry update requests fastapi# 只更新补丁版本poetry update --dry-run# ========== 查看依赖 ==========# 列出所有已安装的包poetry show# 查看依赖树poetry show --tree# 查看指定包的详细信息poetry show requests# 查看哪些依赖有可用更新poetry show --outdated

完整的 pyproject.toml 示例

[tool.poetry]name = "my-awesome-project"version = "0.1.0"description = "一个使用 Poetry 管理的示例项目"authors = ["Your Name <you@example.com>"]maintainers = ["Maintainer <maintainer@example.com>"]readme = "README.md"license = "MIT"homepage = "https://github.com/yourname/my-awesome-project"repository = "https://github.com/yourname/my-awesome-project"documentation = "https://my-awesome-project.readthedocs.io"keywords = ["example", "poetry", "tutorial"]classifiers = [    "Development Status :: 3 - Alpha",    "Intended Audience :: Developers",    "Programming Language :: Python :: 3.12",    "Programming Language :: Python :: 3.13",]# Python 版本约束[tool.poetry.dependencies]python = "^3.12"requests = "^2.31.0"fastapi = ">=0.109.0,<1.0.0"pydantic = "^2.5.0"click = "^8.1.0"# 可选依赖的另一种写法redis = {version = "^5.0", optional = true}# 开发依赖分组[tool.poetry.group.dev.dependencies]pytest = "^8.0.0"pytest-cov = "^5.0.0"black = "^24.0.0"mypy = "^1.8.0"ruff = "^0.3.0"pre-commit = "^3.6.0"[tool.poetry.group.test.dependencies]httpx = "^0.27.0"factory-boy = "^3.3.0"[tool.poetry.group.docs.dependencies]sphinx = "^7.2.0"sphinx-rtd-theme = "^2.0.0"# 命令行入口点[tool.poetry.scripts]mycli = "my_awesome_project.cli:main"# 构建系统配置[build-system]requires = ["poetry-core"]build-backend = "poetry.core.masonry.api"# ========== 工具配置 ==========[tool.black]line-length = 100target-version = ['py312'][tool.ruff]line-length = 100select = ["E", "F", "I", "N", "W", "UP"][tool.mypy]python_version = "3.12"strict = truewarn_return_any = true[tool.pytest.ini_options]testpaths = ["tests"]addopts = "-v --cov=my_awesome_project --cov-report=html"

虚拟环境管理

# Poetry 自动管理虚拟环境# 查看当前虚拟环境信息poetry env info# 列出所有项目虚拟环境poetry env list# 使用指定 Python 版本创建环境poetry env use python3.12poetry env use /usr/bin/python3.13# 在虚拟环境中执行命令poetry run python script.pypoetry run pytestpoetry run black .# 激活虚拟环境(进入子 Shell)poetry shell# 删除虚拟环境poetry env remove python3.12# 配置虚拟环境创建位置(可选)# 在项目目录下创建 .venvpoetry config virtualenvs.in-project true

锁文件 (poetry.lock)

# poetry.lock 的作用:# 1. 记录精确的依赖版本和哈希值# 2. 确保团队所有成员和 CI 环境安装的依赖完全一致# 3. 应该提交到版本控制(Git)# 更新锁文件poetry lock# 更新锁文件但不安装poetry lock --no-update# 重新生成锁文件poetry lock --regenerate

构建与发布

# 构建源码包和 wheelpoetry build# 仅构建 wheelpoetry build -f wheel# 构建前检查配置poetry check# 发布到 PyPIpoetry publish# 先构建再发布poetry publish --build# 发布到 TestPyPIpoetry config repositories.testpypi https://test.pypi.org/legacy/poetry publish --repository testpypi# 设置 PyPI 凭证poetry config pypi-token.pypi your-api-token

2.2 PDM:标准驱动型包管理器

PDM(Python Development Master)是另一个现代包管理器,其最大特色是严格遵循 PEP 标准,支持 PEP 582(本地包目录)。

PDM 的独特优势

  • PEP 582 支持:可在项目本地 __pypackages__ 目录安装包,无需虚拟环境
  • PEP 621 原生支持:直接使用 [project] 标准格式,而非自定义格式
  • 强大的脚本系统:在 pyproject.toml 中定义常用命令
  • 插件生态:支持通过插件扩展功能

安装 PDM

# 官方安装脚本(推荐)# Linux / macOScurl -sSL https://pdm-project.org/install-pdm.py | python3 -# Windows (PowerShell)(Invoke-WebRequest -Uri https://pdm-project.org/install-pdm.py -UseBasicParsing).Content | python -# 使用 pip 安装pip install pdm# 验证安装pdm --version# 启用补全pdm completion bash > ~/.bash_completion

项目初始化

# 创建新项目pdm init my-projectcd my-project# 在已有项目中初始化(交互式)pdm init# 会询问:# - Python 版本# - 项目类型 (library/application)# - 许可证# - 是否使用 src 布局# - 是否添加开发依赖# 生成的结构:# my-project/# ├── src/# │   └── my_project/# │       └── __init__.py# ├── tests/# │   └── __init__.py# ├── pyproject.toml# ├── pdm.lock# └── README.md

依赖管理核心命令

# ========== 添加依赖 ==========# 添加生产依赖pdm add requests fastapi uvicorn# 添加到依赖组pdm add -G dev pytest black mypypdm add -G test pytest-covpdm add -G docs sphinx# 添加可选依赖pdm add -G redis redis# 从 Git 安装pdm add git+https://github.com/org/repo.git@main# 从本地路径安装pdm add ../local-package# 以可编辑模式安装pdm add -e ../local-package# ========== 移除依赖 ==========pdm remove requests# ========== 安装依赖 ==========# 安装所有依赖pdm install# 安装指定组pdm install -G devpdm install -G dev -G test# 安装生产依赖和指定组pdm install --prod -G dev# ========== 更新依赖 ==========# 更新所有依赖pdm update# 更新指定包pdm update requests# 检查过时依赖pdm list --outdated# ========== 查看依赖 ==========# 列出所有依赖pdm list# 查看依赖树pdm list --tree# 导出为 requirements.txtpdm export -o requirements.txtpdm export --group dev -o requirements-dev.txt

pyproject.toml 示例

[project]name = "my-pdm-project"version = "0.1.0"description = "一个使用 PDM 管理的项目"authors = [    { name = "Your Name", email = "you@example.com" }]license = "MIT"readme = "README.md"requires-python = ">=3.12"dependencies = [    "requests>=2.31.0",    "fastapi>=0.109.0",    "click>=8.1.0",][project.optional-dependencies]dev = [    "pytest>=8.0.0",    "pytest-cov>=5.0.0",    "black>=24.0.0",    "mypy>=1.8.0",    "ruff>=0.3.0",]test = ["httpx>=0.27.0"]docs = [    "sphinx>=7.2.0",    "sphinx-rtd-theme>=2.0.0",]all = ["my-pdm-project[dev,test,docs]"][project.scripts]mycli = "my_project.cli:main"[project.urls]Homepage = "https://github.com/yourname/my-pdm-project"Repository = "https://github.com/yourname/my-pdm-project.git"[build-system]requires = ["pdm-backend"]build-backend = "pdm.backend"# ========== PDM 特有配置 ==========[tool.pdm]distribution = true[tool.pdm.dev-dependencies]dev = [    "pytest>=8.0.0",    "black>=24.0.0",    "mypy>=1.8.0",]test = ["pytest-cov>=5.0.0"]# ========== PDM 脚本系统 ==========[tool.pdm.scripts]# 格式:脚本名 = "命令"lint = "ruff check ."format = "black ."check_format = "black --check ."test = "pytest tests/"test_cov = "pytest --cov=my_project tests/"test_cov_html = "pytest --cov=my_project --cov-report=html tests/"type_check = "mypy src/"all_checks = { composite = ["lint", "check_format", "type_check", "test"] }# 带参数的命令serve = "uvicorn my_project.main:app --reload --port 8000"# 调用 Python 函数hello = "my_project:main()"

PDM 脚本系统详解

# PDM 的脚本系统是其最大亮点之一# 运行单个脚本pdm run lintpdm run formatpdm run test# 运行复合脚本(会依次执行多个脚本)pdm run all_checks# 向脚本传递参数pdm run test -k "test_specific" -v# 列出所有可用脚本pdm run --list# 使用预定义钩子脚本[tool.pdm.scripts]# 在安装后自动执行_post_install = "pre-commit install"# 在构建前自动执行_pre_build = "pdm run lint"

构建与发布

# 构建包pdm build# 仅构建 wheelpdm build --no-sdist# 发布到 PyPIpdm publish# 发布到 TestPyPIpdm publish --repository testpypi# 设置 PyPI 凭证pdm config pypi.username __token__pdm config pypi.password your-api-token# 构建并发布pdm publish --build

2.3 Poetry vs PDM 对比与选择

功能对比表

特性
Poetry
PDM
配置格式[tool.poetry]
 自有格式
[project]
 PEP 621 标准格式
虚拟环境
传统虚拟环境(venv)
虚拟环境 + PEP 582 本地目录
依赖解析
快速,基于 SAT 求解器
快速,增量解析
锁文件poetry.lockpdm.lock
依赖分组group.dev-G
 灵活分组
项目脚本
无内置支持
强大的内置脚本系统
插件系统
1.2.0+ 支持
原生支持
发布周期
稳定,大版本发布
频繁,持续更新
社区生态
成熟,文档丰富
快速增长
GitHub Stars
30k+
7k+
PEP 标准遵循
逐步向标准靠拢
从设计之初就遵循

性能对比

# 依赖解析速度(以中型项目为例,约 50 个依赖)# Poetry:首次 ~30s,缓存后 ~5s# PDM:首次 ~20s,缓存后 ~3s# 安装速度# Poetry:~15s# PDM:~10s(增量安装更快)

选择建议

选 Poetry 如果你:

  • 希望工具成熟稳定,社区支持好,遇到问题容易找到解决方案
  • 偏好"开箱即用"的体验,不想过多配置
  • 团队已有 Poetry 使用经验

选 PDM 如果你:

  • 追求最新的 PEP 标准(PEP 621 原生支持)
  • 需要强大的脚本系统来管理开发工作流
  • 希望更灵活的依赖分组和更快的依赖解析

两者均可,如果你:

  • 是新项目,两者都能很好满足需求
  • 是团队项目,关键是统一工具,而非具体选哪个
对比维度
Poetry
PDM
标准化
自有格式,逐步向标准靠拢
从设计之初遵循 PEP 621
脚本系统
无内置支持
强大的内置脚本系统
社区生态
更大(30k+ Stars)
快速增长(7k+ Stars)
学习曲线
适用场景
通用,适合大多数项目
需要复杂工作流,追求标准

2.4 uv:极速包管理器

uv 是由 Astral(ruff 的开发团队)用 Rust 编写的极速 Python 包管理器。它在 2024-2025 年间迅猛发展,已成为 Python 生态中最受瞩目的工具之一。

uv 统一了 pip、pip-tools、pipx、virtualenv、poetry 等多个工具的功能,同时比 pip 快 10-100 倍

操作
pip
Poetry
uv
解析依赖
极快(Rust 实现)
安装包
基准
相似
10-100x 更快
创建虚拟环境
venv
内置
极快
锁文件
无(需 pip-tools)
poetry.lock
uv.lock
项目管理
内置
内置
pip 兼容
完全
部分
完全兼容

安装 uv

# 官方安装脚本(推荐)# Windows (PowerShell)powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"# macOS / Linuxcurl -LsSf https://astral.sh/uv/install.sh | sh# 使用 pip 安装pip install uv# 验证安装uv --version

项目管理(uv 原生模式)

# ========== 创建项目 ==========# 创建库项目(使用 src 布局)uv init my-project --lib# 创建应用项目uv init my-app# 在已有目录中初始化cd existing-projectuv init# ========== 依赖管理 ==========# 添加依赖uv add requests fastapi uvicorn# 添加指定版本uv add "django>=4.2,<5.0"# 添加开发依赖uv add --dev pytest black mypy ruff# 添加可选依赖uv add --optional redis redisuv add --optional gui PyQt5# 移除依赖uv remove requests# 安装所有依赖(自动创建 .venv)uv sync# 仅安装生产依赖uv sync --no-dev# ========== 运行命令 ==========# 在项目环境中运行命令uv run python script.pyuv run pytestuv run black .# 运行一次性工具(替代 pipx)uvx ruff check .uvx black --check .# ========== 更新依赖 ==========# 更新所有依赖uv lock --upgrade# 更新指定包uv lock --upgrade-package requests# 列出过时依赖uv tree --outdated# ========== 构建与发布 ==========# 构建包uv build# 发布到 PyPIuv publish# 发布到 TestPyPIuv publish --publish-url https://test.pypi.org/legacy/

pyproject.toml 示例

[project]name = "my-uv-project"version = "0.1.0"description = "一个使用 uv 管理的项目"readme = "README.md"requires-python = ">=3.12"license = "MIT"authors = [    { name = "Your Name", email = "you@example.com" }]dependencies = [    "requests>=2.31.0",    "fastapi>=0.109.0",    "click>=8.1.0",][project.optional-dependencies]redis = ["redis>=5.0"]gui = ["PyQt5>=5.15.0"][dependency-groups]dev = [    "pytest>=8.0.0",    "pytest-cov>=5.0.0",    "black>=24.0.0",    "mypy>=1.8.0",    "ruff>=0.3.0",]test = ["httpx>=0.27.0"]docs = [    "sphinx>=7.2.0",    "sphinx-rtd-theme>=2.0.0",][project.scripts]mycli = "my_uv_project.cli:main"[project.urls]Homepage = "https://github.com/yourname/my-uv-project"Repository = "https://github.com/yourname/my-uv-project.git"[build-system]requires = ["uv_build>=0.9.0,<0.10.0"]build-backend = "uv_build"# ========== 工具配置 ==========[tool.ruff]line-length = 100select = ["E", "F", "I", "N", "W", "UP"][tool.pytest.ini_options]testpaths = ["tests"]addopts = "-v --cov=my_uv_project --cov-report=html"

uv 作为 pip 的直接替代

# uv 完全兼容 pip 接口,可直接替换uv pip install requests       # 替代 pip installuv pip install -r requirements.txtuv pip freeze > requirements.txtuv pip listuv pip uninstall requests# 创建虚拟环境uv venv                        # 替代 python -m venv .venvuv venv --python 3.12# 编译依赖(替代 pip-tools)uv pip compile requirements.in -o requirements.txtuv pip compile pyproject.toml -o requirements.txt

uv 与 Poetry/PDM 对比

特性
uv
Poetry
PDM
速度
极快(Rust)
快(Python)
快(Python)
配置格式
PEP 621 标准
[tool.poetry]
 自有
PEP 621 标准
pip 兼容
完全兼容
部分兼容
部分兼容
锁文件
uv.lock(跨平台)
poetry.lock
pdm.lock
项目初始化
uv init
poetry new
pdm init
工具运行
uvx(内置)
需 pipx
需 pipx
GitHub Stars
50k+
30k+
7k+

选择建议

选 uv 如果你:

  • 追求极致速度,特别是在 CI/CD 中
  • 希望一个工具覆盖 pip + virtualenv + pip-tools + pipx 的所有功能
  • 需要与现有 pip 工作流无缝切换
  • 看重锁文件的跨平台能力

2.5 Hatch:PyPA 官方项目管理工具

Hatch 是 PyPA(Python Packaging Authority)官方推出的现代化项目管理工具。它提供插件系统、环境管理和版本管理等功能。

# 安装pip install hatch# 创建新项目hatch new my-project# 在已有项目中初始化hatch new --init# 创建环境hatch env create# 运行命令hatch run pytesthatch run lint:ruff check .# 构建hatch build# 发布hatch publish# 版本管理hatch version minor    # 0.1.0 → 0.2.0hatch version patch    # 0.1.0 → 0.1.1

Hatch 的最大特点是完全基于 PEP 标准强大的插件系统(可自定义构建后端、环境类型、版本方案等),适合需要高度定制化的项目。


3. 生成可独立运行的应用程序

当你的用户不想(或不能)安装 Python 环境时,你需要将程序打包成独立的可执行文件。

3.1 打包工具工作原理

3.2 PyInstaller:最通用的应用打包器

PyInstaller 是使用最广泛的 Python 应用打包工具。它通过分析你的入口脚本,递归找到所有依赖,并将其与一个精简的 Python 解释器打包在一起。

安装

pip install pyinstaller# 验证安装pyinstaller --version

基本命令

# ========== 基础用法 ==========# 默认打包(生成文件夹,包含所有依赖)pyinstaller script.py# 打包为单个文件(方便分发)pyinstaller --onefile script.py# GUI 应用(Windows/macOS 不显示控制台黑框)pyinstaller --windowed gui_app.py# 指定输出名称pyinstaller --name MyApp script.py# 指定图标pyinstaller --icon=app.ico script.py# 指定输出目录pyinstaller --distpath=./release script.py# ========== 进阶选项 ==========# 添加数据文件(Windows 用分号分隔)# Linux/macOSpyinstaller --add-data "src/config:config" script.py# Windowspyinstaller --add-data "src/config;config" script.py# 添加隐式导入(手动指定分析不到的模块)pyinstaller --hidden-import=module_name script.py# 排除不需要的模块(减小体积)pyinstaller --exclude-module=tkinter script.py# 清理构建文件后打包pyinstaller --clean --onefile script.py# 不压缩(加快启动速度,但文件更大)pyinstaller --noupx --onefile script.py

.spec 文件详解

当打包需求变复杂时,推荐使用 .spec 文件:

# -*- mode: python ; coding: utf-8 -*-# myapp.specblock_cipher = None# 1. 分析阶段:分析所有依赖a = Analysis(    ['main.py'],                          # 入口脚本    pathex=[],                           # 搜索路径    binaries=[],                         # 额外的二进制文件    datas=[        ('resources/images/*''images'),  # (源路径, 目标路径)        ('resources/config.yaml''.'),    # 配置文件        ('resources/fonts/*.ttf''fonts'),    ],    hiddenimports=['module1','module2.submodule',    ],    hookspath=[],                        # 自定义 hook 路径    hooksconfig={},                      # hook 配置    runtime_hooks=[],                    # 运行时 hook    excludes=['tkinter',                       # 排除不需要的模块'matplotlib','IPython',    ],    win_no_prefer_redirects=False,    win_private_assemblies=False,    cipher=block_cipher,    noarchive=False,)# 2. 合并 Python 字节码pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)# 3. 生成可执行文件exe = EXE(    pyz,    a.scripts,    a.binaries,    a.zipfiles,    a.datas,    [],    name='MyApp',                        # 可执行文件名    debug=False,                         # 调试模式    bootloader_ignore_signals=False,    strip=False,                         # 移除符号信息    upx=True,                            # 使用 UPX 压缩    upx_exclude=[],                      # 不压缩的文件    runtime_tmpdir=None,                 # 临时解压目录    console=True,                        # 是否显示控制台    disable_windowed_traceback=False,    argv_emulation=False,    target_arch=None,                    # 目标架构    codesign_identity=None,              # macOS 代码签名    entitlements_file=None,              # macOS 权限文件    icon='app.ico',                      # 应用图标)# 4. 如果打包为文件夹模式,还可以生成 COLLECT# coll = COLLECT(#     exe,#     a.binaries,#     a.zipfiles,#     a.datas,#     strip=False,#     upx=True,#     upx_exclude=[],#     name='MyApp',# )

使用 spec 文件:

# 第一次打包时生成 spec 文件pyinstaller --onefile --name MyApp main.py# 后续直接使用 spec 文件打包pyinstaller myapp.spec# 修改 spec 文件后重新打包pyinstaller --clean myapp.spec

处理资源文件

# resource_manager.pyimport sysimport osfrom pathlib import Pathfrom typing import Uniondefget_resource_path(relative_path: Union[str, Path]) -> str:"""    获取资源文件的绝对路径,兼容开发环境和打包环境。    在开发环境中,返回相对于项目根目录的路径。    在 PyInstaller 打包环境中,返回相对于临时解压目录的路径。    Args:        relative_path: 相对路径    Returns:        资源的绝对路径    Example:        >>> config_path = get_resource_path("config/app.yaml")        >>> with open(config_path) as f:        ...     config = yaml.safe_load(f)    """# 检查是否在 PyInstaller 打包环境中if getattr(sys, 'frozen'False):# 打包环境:使用 _MEIPASS(临时解压目录)        base_path = sys._MEIPASSelse:# 开发环境:使用项目根目录        base_path = os.path.abspath(".")return os.path.join(base_path, relative_path)defget_app_root() -> str:"""    获取应用程序根目录(可执行文件所在目录)。    Returns:        应用程序根目录的绝对路径    """if getattr(sys, 'frozen'False):# 打包环境:可执行文件所在目录return os.path.dirname(sys.executable)else:# 开发环境:脚本所在目录return os.path.dirname(os.path.abspath(sys.argv[0]))# 使用示例if __name__ == "__main__":# 读取打包到应用内部的配置文件    config_path = get_resource_path("data/config.json")# 读写用户数据(放在可执行文件同目录下)    user_data_path = os.path.join(get_app_root(), "user_data.json")

常见问题

# 问题1:打包后文件太大# 解决方案:# 1. 在虚拟环境中打包,只安装必要依赖python -m venv build_envsource build_env/bin/activatepip install your-app-depspip install pyinstallerpyinstaller --onefile main.py# 2. 排除不需要的模块pyinstaller --exclude-module=tkinter --exclude-module=matplotlib main.py# 问题2:缺少某些模块# 解决方案:使用 --hidden-importpyinstaller --hidden-import=scipy.special._ufuncs main.py# 问题3:DLL 缺失错误(Windows)# 解决方案:手动添加 DLLpyinstaller --add-binary "C:/path/to/missing.dll;." main.py# 问题4:打包后程序崩溃但开发环境正常# 解决方案:使用 --debug 模式查找问题pyinstaller --debug=all main.py# 问题5:相对导入失败(ImportError: attempted relative import)# 原因:PyInstaller 直接执行脚本,但脚本使用了相对导入(如 from .core import xxx)# 解决方案1:将入口脚本改为绝对导入# from .core import Calculator  →  from calculator.core import Calculator# 解决方案2:用 -m 模块方式指定入口pyinstaller --onefile -n myapp -m calculator.cli# 解决方案3:创建一个不带相对导入的外层启动脚本 wrapper.py# wrapper.py 内容:from calculator.cli import main; main()

3.3 Nuitka:追求性能的编译器

Nuitka 将 Python 代码编译为 C/C++,然后生成原生可执行文件。这不仅能打包,还能提升性能和保护源代码。

安装

pip install nuitka# 验证安装python -m nuitka --version# 确保有 C 编译器# Linux: sudo apt install gcc# macOS: xcode-select --install# Windows: 安装 Visual Studio Build Tools 或 MinGW

基本命令

# ========== 基础用法 ==========# 编译为独立文件夹python -m nuitka --standalone script.py# 编译为单个文件python -m nuitka --standalone --onefile script.py# GUI 应用(不显示控制台)# Windowspython -m nuitka --standalone --windows-console-mode=disable gui.py# macOSpython -m nuitka --standalone --macos-create-app-bundle gui.py# 指定输出目录python -m nuitka --standalone --output-dir=build script.py# ========== 优化选项 ==========# 启用链接时优化(LTO)python -m nuitka --standalone --onefile --lto=yes script.py# 移除断言和文档字符串(减少体积)python -m nuitka --standalone --onefile --python-flag=no_site,no_warnings script.py# 使用 Clang 编译器(macOS 上更快)python -m nuitka --standalone --clang script.py# 启用所有优化(生产环境推荐)python -m nuitka --standalone \    --onefile \    --lto=yes \    --remove-output \    --python-flag=no_site,no_warnings \    script.py

性能对比

# benchmark.pyimport timedeffibonacci(n):"""递归计算斐波那契数(计算密集型)"""if n <= 1:return nreturn fibonacci(n-1) + fibonacci(n-2)defmain():    start = time.time()    result = fibonacci(35)    elapsed = time.time() - start    print(f"Result: {result}")    print(f"Time: {elapsed:.3f}s")if __name__ == "__main__":    main()
# 运行基准测试python benchmark.py# 结果示例: Time: 2.134s# 使用 Nuitka 编译后运行python -m nuitka --standalone --onefile benchmark.py./benchmark# 结果示例: Time: 1.523s (约 30% 提升)# PyInstaller 打包后运行pyinstaller --onefile benchmark.py./dist/benchmark# 结果示例: Time: 2.145s (几乎无提升)

Nuitka 与 PyInstaller 对比

特性
Nuitka
PyInstaller
原理
编译 Python→C→机器码
打包 Python 字节码 + 解释器
性能提升
10-30%
几乎无提升
启动速度
极快,近乎原生
慢(需解压,--onefile 下尤甚)
文件大小
可能更大
相对较小
编译时间
非常长(数分钟到数十分钟)
快(秒级)
代码保护
极好(编译为机器码)
一般(.pyc 可反编译)
兼容性
极好(遵循 Python 语义)
很好(少数动态特性可能遗漏)
成熟度
成熟,商业化支持
非常成熟,社区庞大
调试难度
较难(编译后难以调试)
较易(仍为 Python)

使用建议

  • 开发阶段:使用 PyInstaller(快速迭代)
  • 最终发布:使用 Nuitka(性能和保护)
  • 科学计算/数值密集型应用:强烈建议尝试 Nuitka,性能提升可能非常可观

3.4 跨平台打包指南

各平台注意事项

平台
可执行格式
构建要求
分发注意事项
Windows.exe
只能在 Windows 上构建
可能需要安装 VC++ Redistributable
macOS.app
 / Unix 可执行文件
只能在 macOS 上构建
需要代码签名和公证
Linux
ELF 可执行文件
在旧版本发行版上构建
注意 glibc 版本兼容性

macOS 代码签名

# 1. 创建 entitlements.plistcat > entitlements.plist << EOF<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN""http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><dict>    <key>com.apple.security.cs.allow-unsigned-executable-memory</key>    <true/>    <key>com.apple.security.cs.disable-library-validation</key>    <true/></dict></plist>EOF# 2. 签名codesign --deep --force --verify --verbose \    --sign "Developer ID Application: Your Name (XXXXXXXXXX)" \    --entitlements entitlements.plist \    --options runtime \    dist/MyApp.app# 3. 创建 DMGcreate-dmg --volname "MyApp" \    --window-pos 200 120 \    --window-size 800 400 \    --icon-size 100 \    --icon "MyApp.app" 200 190 \    --hide-extension "MyApp.app" \    --app-drop-link 600 185 \"MyApp.dmg" \"dist/MyApp.app"# 4. 公证xcrun notarytool submit MyApp.dmg \    --apple-id "your@email.com" \    --team-id "XXXXXXXXXX" \    --password "@keychain:AC_PASSWORD" \    --wait

Linux AppImage 制作

# 1. 创建 AppDir 结构mkdir -p AppDir/usr/binmkdir -p AppDir/usr/share/iconsmkdir -p AppDir/usr/share/applications# 2. 复制可执行文件cp dist/MyApp AppDir/usr/bin/# 3. 创建 desktop 文件cat > AppDir/usr/share/applications/myapp.desktop << EOF[Desktop Entry]Name=MyAppExec=MyAppIcon=myappType=ApplicationCategories=Utility;EOF# 4. 创建 AppRuncat > AppDir/AppRun << 'EOF'#!/bin/bashSELF=$(readlink -f "$0")HERE=${SELF%/*}export PATH="${HERE}/usr/bin/:${PATH}"exec"${HERE}/usr/bin/MyApp""$@"EOFchmod +x AppDir/AppRun# 5. 下载 appimagetool 并制作 AppImagewget https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImagechmod +x appimagetool-x86_64.AppImage./appimagetool-x86_64.AppImage AppDir MyApp.AppImage

3.5 案例:GUI 应用打包

GUI 应用示例

# gui_app.py"""一个简单的图片查看器,演示 GUI 应用的打包。"""import sysimport osfrom pathlib import Pathfrom typing import Optionaltry:from PyQt5.QtWidgets import (        QApplication, QMainWindow, QLabel, QFileDialog,        QVBoxLayout, QWidget, QPushButton, QStatusBar    )from PyQt5.QtGui import QPixmap, QIconfrom PyQt5.QtCore import Qtexcept ImportError:    print("请安装 PyQt5: pip install PyQt5")    sys.exit(1)defresource_path(relative_path: str) -> str:"""获取资源路径,兼容开发环境和打包环境."""if getattr(sys, 'frozen'False):        base_path = sys._MEIPASSelse:        base_path = os.path.abspath(".")return os.path.join(base_path, relative_path)classImageViewer(QMainWindow):"""图片查看器主窗口."""def__init__(self):        super().__init__()        self.current_image_path: Optional[str] = None        self.init_ui()definit_ui(self):"""初始化 UI."""        self.setWindowTitle("图片查看器 v1.0")        self.setGeometry(100100800600)# 设置图标        icon_path = resource_path("resources/icon.png")if os.path.exists(icon_path):            self.setWindowIcon(QIcon(icon_path))# 中央部件        central_widget = QWidget()        self.setCentralWidget(central_widget)        layout = QVBoxLayout()        central_widget.setLayout(layout)# 图片显示标签        self.image_label = QLabel("请打开一张图片")        self.image_label.setAlignment(Qt.AlignCenter)        self.image_label.setStyleSheet("QLabel { background-color: #f0f0f0; border: 2px dashed #ccc; }"        )        layout.addWidget(self.image_label)# 按钮        btn_open = QPushButton("打开图片")        btn_open.clicked.connect(self.open_image)        layout.addWidget(btn_open)# 状态栏        self.status_bar = QStatusBar()        self.setStatusBar(self.status_bar)        self.status_bar.showMessage("就绪")defopen_image(self):"""打开图片文件."""        file_path, _ = QFileDialog.getOpenFileName(            self,"选择图片","","图片文件 (*.png *.jpg *.jpeg *.bmp *.gif);;所有文件 (*)"        )if file_path:            self.load_image(file_path)defload_image(self, file_path: str):"""加载并显示图片."""        pixmap = QPixmap(file_path)if pixmap.isNull():            self.status_bar.showMessage(f"无法加载图片: {file_path}")return# 缩放图片以适应窗口        scaled_pixmap = pixmap.scaled(            self.image_label.size(),            Qt.KeepAspectRatio,            Qt.SmoothTransformation        )        self.image_label.setPixmap(scaled_pixmap)        self.current_image_path = file_path# 更新状态栏        file_name = os.path.basename(file_path)        size = pixmap.size()        self.status_bar.showMessage(f"{file_name} | {size.width()}x{size.height()} 像素"        )defresizeEvent(self, event):"""窗口大小改变时重新缩放图片."""        super().resizeEvent(event)if self.current_image_path and self.image_label.pixmap():            self.load_image(self.current_image_path)defmain():"""主函数."""    app = QApplication(sys.argv)# 设置应用样式    app.setStyle('Fusion')    viewer = ImageViewer()    viewer.show()    sys.exit(app.exec_())if __name__ == '__main__':    main()

打包命令

# PyInstaller 打包pyinstaller --onefile \    --windowed \    --name "ImageViewer" \    --icon resources/icon.ico \    --add-data "resources:resources" \    gui_app.py# Nuitka 打包(更推荐用于最终发布)python -m nuitka --standalone \    --onefile \    --windows-console-mode=disable \    --windows-icon-from-ico=resources/icon.ico \    --enable-plugins=pyqt5 \    --include-data-dir=resources=resources \    --output-dir=build \    --remove-output \    --lto=yes \    gui_app.py

4. Docker 容器化部署

对于 Web 服务、API、后台任务等,Docker 是最佳选择。

问题
无 Docker
有 Docker
环境一致性
"在我电脑上能跑"
所有环境完全一致
依赖管理
手动安装系统依赖
在 Dockerfile 中声明
部署
手动配置服务器
docker run
 一键部署
扩展
复杂
配合编排工具轻松扩展
回滚
困难
切换镜像版本即可

多阶段构建 Dockerfile

# ========== 构建阶段 ==========FROM python:3.13-slim AS builderWORKDIR /app# 安装系统构建依赖RUN apt-get update && apt-get install -y --no-install-recommends \    gcc \    g++ \    && rm -rf /var/lib/apt/lists/*# 复制项目配置文件COPY pyproject.toml README.md ./COPY src/ ./src/# 构建 wheel 包RUN pip install --no-cache-dir build && \    python -m build --wheel && \# 提取依赖信息    pip install --no-cache-dir pip-tools && \    pip-compile --generate-hashes -o requirements.txt pyproject.toml# ========== 运行阶段 ==========FROM python:3.13-slimWORKDIR /app# 安装运行时系统依赖RUN apt-get update && apt-get install -y --no-install-recommends \    ca-certificates \    curl \    && rm -rf /var/lib/apt/lists/*# 从构建阶段复制 wheelCOPY --from=builder /app/dist/*.whl ./dist/# 安装应用RUN pip install --no-cache-dir dist/*.whl && \    rm -rf dist/# 创建非 root 用户(安全最佳实践)RUN groupadd -r appuser && \    useradd -r -g appuser -m -s /bin/bash appuser && \    chown -R appuser:appuser /app# 切换到非 root 用户USER appuser# 健康检查HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \    CMD curl -f http://localhost:8000/health || exit 1# 暴露端口EXPOSE8000# 设置环境变量ENV PYTHONUNBUFFERED=1ENV PYTHONDONTWRITEBYTECODE=1# 入口点ENTRYPOINT ["uvicorn""myapp.main:app""--host""0.0.0.0""--port""8000"]

Docker Compose 编排

# docker-compose.ymlversion:'3.8'services:app:build:context:.dockerfile:Dockerfileimage:myapp:latestcontainer_name:myapprestart:unless-stoppedports:-"8000:8000"environment:-DATABASE_URL=postgresql://user:pass@db:5432/mydb-REDIS_URL=redis://redis:6379/0-LOG_LEVEL=INFOvolumes:-./data:/app/data-./logs:/app/logsdepends_on:db:condition:service_healthyredis:condition:service_healthynetworks:-app-networkhealthcheck:test:["CMD","curl","-f","http://localhost:8000/health"]interval:30stimeout:5sretries:3start_period:10sdb:image:postgres:16-alpinecontainer_name:myapp-dbrestart:unless-stoppedenvironment:-POSTGRES_USER=user-POSTGRES_PASSWORD=pass-POSTGRES_DB=mydbvolumes:-postgres_data:/var/lib/postgresql/datanetworks:-app-networkhealthcheck:test:["CMD-SHELL","pg_isready -U user -d mydb"]interval:10stimeout:5sretries:5redis:image:redis:7-alpinecontainer_name:myapp-redisrestart:unless-stoppedvolumes:-redis_data:/datanetworks:-app-networkhealthcheck:test:["CMD","redis-cli","ping"]interval:10stimeout:5sretries:5volumes:postgres_data:redis_data:networks:app-network:driver:bridge

构建与部署

# 构建镜像docker build -t myapp:latest .# 使用 docker-compose 启动docker-compose up -d# 查看日志docker-compose logs -f app# 扩展服务docker-compose up -d --scale app=3# 更新服务docker-compose pull appdocker-compose up -d app# 停止服务docker-compose down# 推送到镜像仓库docker tag myapp:latest registry.example.com/myapp:latestdocker push registry.example.com/myapp:latest

5. Conda 打包

Conda 是数据科学领域的事实标准。它不仅管理 Python 包,还管理非 Python 系统依赖。

Conda 包的结构

my-conda-package/├── recipe/│   ├── meta.yaml          # 包元数据(核心文件)│   ├── build.sh           # Linux/macOS 构建脚本│   └── bld.bat            # Windows 构建脚本├── src/                   # 源代码│   └── mypackage/│       ├── __init__.py│       └── core.py└── tests/    └── test_package.py

meta.yaml 完整示例

{%setname="mypackage"%}{%setversion="0.1.0"%}package:name:{{name}}version:{{version}}source:path:..build:number:0script:{{PYTHON}}-mpipinstall.--no-deps--no-build-isolation-vventry_points:-mycli=mypackage.cli:main# 跳过特定平台# skip: True  # [win]# skip: True  # [py<38]requirements:build:-{{compiler('c')}}-{{compiler('cxx')}}host:-python-pip-setuptools-wheelrun:-python-numpy>=1.20.0# 声明非 Python 依赖-pandas>=1.3.0-scipy>=1.7.0-requests>=2.28.0-click>=8.0.0test:imports:-mypackage-mypackage.corecommands:-mycli--help-python-c"from mypackage import Calculator; c=Calculator(); assert c.add(1,2)==3"requires:-pytest-pytest-covabout:home:https://github.com/yourname/mypackagelicense:MITlicense_file:LICENSEsummary:"一个示例 Conda 包"description:|    这是一个演示如何构建 Conda 包的示例项目。doc_url:https://mypackage.readthedocs.iodev_url:https://github.com/yourname/mypackageextra:recipe-maintainers:-yourname

构建和发布

# 安装 conda-buildconda install conda-build# 构建包conda-build recipe/# 构建并指定输出目录conda-build --output-folder ./output recipe/# 构建特定 Python 版本conda-build --python 3.12 recipe/# 安装本地构建的包conda install --use-local mypackage# 上传到 Anaconda Cloudanaconda upload /path/to/linux-64/mypackage-0.1.0-py312_0.tar.bz2# 创建自定义频道mkdir -p /path/to/channel/linux-64cp /path/to/mypackage-0.1.0-py312_0.tar.bz2 /path/to/channel/linux-64/conda index /path/to/channel# 然后添加频道: conda config --add channels file:///path/to/channel

6. CI/CD 自动化发布

GitHub Actions 完整工作流

# .github/workflows/build-and-release.ymlname:BuildandReleaseon:push:branches:[main]tags:-'v*'# 推送 v 开头的 tag 时触发发布pull_request:branches:[main]workflow_dispatch:# 允许手动触发jobs:# ========== 测试 ==========test:name:Test(Python${{matrix.python-version}})runs-on:ubuntu-lateststrategy:matrix:python-version:['3.12','3.13']steps:-uses:actions/checkout@v4-name:SetupPython${{matrix.python-version}}uses:actions/setup-python@v5with:python-version:${{matrix.python-version}}-name:Installdependenciesrun:|        python -m pip install --upgrade pip        pip install ".[dev]"-name:Lintwithruffrun:ruffcheck.-name:Typecheckwithmypyrun:mypysrc/-name:Testwithpytestrun:pytest--cov=mypackage--cov-report=xml-name:UploadcoveragetoCodecovuses:codecov/codecov-action@v4with:file:./coverage.xmlfail_ci_if_error:true# ========== 构建 Python 包 ==========build-package:name:BuildPythonPackageneeds:testruns-on:ubuntu-latestif:startsWith(github.ref,'refs/tags/')steps:-uses:actions/checkout@v4-name:SetupPythonuses:actions/setup-python@v5with:python-version:'3.12'-name:Buildpackagerun:|        pip install build        python -m build-name:Checkpackagerun:|        pip install twine        twine check dist/*-name:Uploadpackageartifactsuses:actions/upload-artifact@v4with:name:python-packagepath:dist/# ========== 构建可执行文件(多平台) ==========build-binary:name:BuildBinary(${{matrix.os}})needs:testruns-on:${{matrix.os}}if:startsWith(github.ref,'refs/tags/')strategy:matrix:include:-os:ubuntu-latestartifact_name:myapp-linuxasset_name:myapp-linux-os:windows-latestartifact_name:myapp-windowsasset_name:myapp-windows.exe-os:macos-latestartifact_name:myapp-macosasset_name:myapp-macossteps:-uses:actions/checkout@v4-name:SetupPythonuses:actions/setup-python@v5with:python-version:'3.12'-name:Installdependenciesrun:|        pip install pyinstaller        pip install ".[all]"-name:BuildwithPyInstallerrun:|        pyinstaller --onefile \          --name ${{ matrix.asset_name }} \          --add-data "src/mypackage/data:data" \          src/mypackage/cli.py-name:Uploadbinaryartifactuses:actions/upload-artifact@v4with:name:${{matrix.artifact_name}}path:dist/${{matrix.asset_name}}${{matrix.os=='windows-latest'&&'.exe'||''}}# ========== 构建 Docker 镜像 ==========build-docker:name:BuildDockerImageneeds:testruns-on:ubuntu-latestif:startsWith(github.ref,'refs/tags/')steps:-uses:actions/checkout@v4-name:SetupDockerBuildxuses:docker/setup-buildx-action@v3-name:LogintoDockerHubuses:docker/login-action@v3with:username:${{secrets.DOCKER_USERNAME}}password:${{secrets.DOCKER_PASSWORD}}-name:BuildandpushDockerimageuses:docker/build-push-action@v5with:context:.push:truetags:|          yourusername/myapp:latest          yourusername/myapp:${{ github.ref_name }}# ========== 发布 GitHub Release ==========release:name:CreateGitHubReleaseneeds:[build-package,build-binary,build-docker]runs-on:ubuntu-latestif:startsWith(github.ref,'refs/tags/')steps:-name:Downloadallartifactsuses:actions/download-artifact@v4-name:Displaystructureofdownloadedfilesrun:ls-R-name:CreateReleaseuses:softprops/action-gh-release@v1with:name:Release${{github.ref_name}}body:|          ## 🚀 Release ${{ github.ref_name }}### 📦 安装方式**pip安装:**```bashpipinstallmyapp```**Docker运行:**```bashdockerrun-p8000:8000yourusername/myapp:${{github.ref_name}}```### 📥 下载见下方Assets列表。### 📝 更新日志-[CHANGELOG.md](CHANGELOG.md)draft:falseprerelease:falsefiles:|          python-package/*          myapp-linux/myapp-linux          myapp-windows/myapp-windows.exe          myapp-macos/myapp-macosenv:GITHUB_TOKEN:${{secrets.GITHUB_TOKEN}}

常见问题

问题 1:打包后找不到数据文件

# ❌ 错误写法config_path = os.path.join(os.path.dirname(__file__), "data/config.json")# ✅ 正确写法import sysimport osdefget_resource_path(relative_path: str) -> str:"""获取资源文件路径,兼容开发和打包环境."""if getattr(sys, 'frozen'False):# PyInstaller 打包后的临时目录        base_path = sys._MEIPASSelse:# 开发环境        base_path = os.path.abspath(".")return os.path.join(base_path, relative_path)config_path = get_resource_path("data/config.json")

问题 2:打包后文件体积过大

# 方法 1:在干净的虚拟环境中打包python -m venv build_envsource build_env/bin/activatepip install --no-cache-dir myapppip install pyinstallerpyinstaller --onefile myapp.py# 方法 2:排除不需要的模块pyinstaller --exclude-module=tkinter \    --exclude-module=matplotlib \    --exclude-module=IPython \    --onefile myapp.py# 方法 3:使用 UPX 压缩# 先安装 UPX: https://upx.github.io/pyinstaller --upx-dir=/path/to/upx --onefile myapp.py# 方法 4:Nuitka 配合 LTOpython -m nuitka --standalone --onefile --lto=yes myapp.py

问题 3:缺少隐式导入的模块

# PyInstaller 无法分析动态导入# 例如:importlib.import_module(f"plugins.{name}")# 解决方案:手动指定pyinstaller --hidden-import=plugins.plugin1 \    --hidden-import=plugins.plugin2 \    --onefile myapp.py# 或者在 spec 文件中添加a = Analysis(    ['main.py'],    hiddenimports=['plugins.plugin1''plugins.plugin2'],)

问题 4:运行时性能问题

# 问题:--onefile 模式启动慢# 原因:每次运行都需要解压到临时目录# 解决方案 1:使用 --onedir(推荐用于频繁运行的程序)pyinstaller --onedir myapp.py# 解决方案 2:使用 Nuitka(启动速度极快)python -m nuitka --standalone --onefile myapp.py# 解决方案 3:使用 --runtime-tmpdir 指定固定解压目录pyinstaller --onefile --runtime-tmpdir /tmp/myapp_cache myapp.py

问题 5:代码保护

# 保护等级从低到高:# Level 1: 编译为 .pyc(PyInstaller 默认)# 可被 uncompyle6 等工具反编译# Level 2: 混淆代码# 使用 pyarmorpip install pyarmorpyarmor obfuscate myapp.py# Level 3: 编译为机器码(Nuitka)python -m nuitka --standalone --onefile myapp.py# Level 4: 编译核心模块为 C 扩展(Cython)# setup.pyfrom setuptools import setupfrom Cython.Build import cythonizesetup(    ext_modules=cythonize("core.py", language_level="3"),)

总结

快速决策指南

选择优先级:

  1. 先确定目标用户(开发者还是最终用户)
  2. 再确定应用类型(库、桌面应用、Web服务还是数据科学工具)
  3. 最后根据具体需求(性能、易用性、标准化)选择具体工具
场景
推荐工具
一句话理由
开发 Python 库(速度优先)
uv
极速,统一工具链,pip 完全兼容
开发 Python 库(成熟稳定)
Poetry
社区大,开箱即用,文档丰富
需要自定义工作流
PDM
脚本系统强大,标准兼容性高
需要高度定制化
Hatch
官方出品,插件系统强大
简单脚本分发
PyInstaller
简单快速,生态成熟
商业桌面应用
Nuitka
性能好,代码保护强
Web 服务部署
Docker
环境一致性,易于扩展
数据科学工具
Conda
处理非 Python 依赖
CI/CD 自动化
GitHub Actions
与 GitHub 集成,免费额度

工具选择速查表

场景
推荐工具
原因
开发 Python 库(速度优先)
uv
极速,统一工具链,2025年已成主流
开发 Python 库(成熟稳定)
Poetry
社区大,文档丰富
需要自定义脚本
PDM
脚本系统强大,标准兼容性高
需要高度定制化
Hatch
官方出品,插件系统强大
简单脚本分发
PyInstaller
简单快速,生态成熟
商业桌面应用
Nuitka
性能好,代码保护强
Web 服务部署
Docker
环境一致性,易于扩展
数据科学工具
Conda
处理非 Python 依赖
CI/CD 自动化
GitHub Actions
与 GitHub 集成,免费额度

建议

  1. 不要过度工程化:刚开始时,简单的 pip install -e . 或 uv pip install -e . 就足够开发使用了
  2. 先发布,后优化:先让你的包/应用能跑起来,再逐步优化打包流程
  3. 自动化一切:尽早设置 CI/CD,让构建、测试、发布全自动(uv 在 CI 中速度优势巨大)
  4. 文档很重要:好的 README 和安装说明能减少 90% 的用户问题
  5. 关注安全:使用 Docker 时用非 root 用户,PyPI 发布时用 API Token

祝你打包顺利!


作者简介:码上工坊,探索用编程为己赋能,定期分享编程知识和项目实战经验。持续学习、适应变化、记录点滴、复盘反思、成长进步。

重要提示:本文主要是记录自己的学习与实践过程,所提内容或者观点仅代表个人意见,只是我以为的,不代表完全正确,欢迎交流讨论。

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-05-27 20:48:55 HTTP/1.1 GET : https://www.yeyulingfeng.com/a/663644.html
  2. 运行时间 : 0.087854s [ 吞吐率:11.38req/s ] 内存消耗:5,046.27kb 文件加载:145
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=8551618b8c9940ec730159795714716e
  1. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/public/index.php ( 0.79 KB )
  2. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/autoload.php ( 0.17 KB )
  3. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/composer/autoload_real.php ( 2.49 KB )
  4. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/composer/platform_check.php ( 0.90 KB )
  5. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/composer/ClassLoader.php ( 14.03 KB )
  6. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/composer/autoload_static.php ( 6.05 KB )
  7. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/helper.php ( 8.34 KB )
  8. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-validate/src/helper.php ( 2.19 KB )
  9. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/ralouphie/getallheaders/src/getallheaders.php ( 1.60 KB )
  10. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/helper.php ( 1.47 KB )
  11. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/stubs/load_stubs.php ( 0.16 KB )
  12. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Exception.php ( 1.69 KB )
  13. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-container/src/Facade.php ( 2.71 KB )
  14. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/deprecation-contracts/function.php ( 0.99 KB )
  15. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/polyfill-mbstring/bootstrap.php ( 8.26 KB )
  16. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/polyfill-mbstring/bootstrap80.php ( 9.78 KB )
  17. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/var-dumper/Resources/functions/dump.php ( 1.49 KB )
  18. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-dumper/src/helper.php ( 0.18 KB )
  19. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/var-dumper/VarDumper.php ( 4.30 KB )
  20. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/guzzlehttp/guzzle/src/functions_include.php ( 0.16 KB )
  21. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/guzzlehttp/guzzle/src/functions.php ( 5.54 KB )
  22. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/App.php ( 15.30 KB )
  23. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-container/src/Container.php ( 15.76 KB )
  24. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/psr/container/src/ContainerInterface.php ( 1.02 KB )
  25. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/provider.php ( 0.19 KB )
  26. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Http.php ( 6.04 KB )
  27. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/helper/Str.php ( 7.29 KB )
  28. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Env.php ( 4.68 KB )
  29. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/common.php ( 0.03 KB )
  30. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/helper.php ( 18.78 KB )
  31. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Config.php ( 5.54 KB )
  32. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/alipay.php ( 3.59 KB )
  33. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/facade/Env.php ( 1.67 KB )
  34. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/app.php ( 0.95 KB )
  35. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/cache.php ( 0.78 KB )
  36. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/console.php ( 0.23 KB )
  37. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/cookie.php ( 0.56 KB )
  38. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/database.php ( 2.48 KB )
  39. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/filesystem.php ( 0.61 KB )
  40. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/lang.php ( 0.91 KB )
  41. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/log.php ( 1.35 KB )
  42. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/middleware.php ( 0.19 KB )
  43. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/route.php ( 1.89 KB )
  44. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/session.php ( 0.57 KB )
  45. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/trace.php ( 0.34 KB )
  46. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/view.php ( 0.82 KB )
  47. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/event.php ( 0.25 KB )
  48. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Event.php ( 7.67 KB )
  49. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/service.php ( 0.13 KB )
  50. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/AppService.php ( 0.26 KB )
  51. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Service.php ( 1.64 KB )
  52. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Lang.php ( 7.35 KB )
  53. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/lang/zh-cn.php ( 13.70 KB )
  54. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/initializer/Error.php ( 3.31 KB )
  55. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/initializer/RegisterService.php ( 1.33 KB )
  56. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/services.php ( 0.14 KB )
  57. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/service/PaginatorService.php ( 1.52 KB )
  58. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/service/ValidateService.php ( 0.99 KB )
  59. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/service/ModelService.php ( 2.04 KB )
  60. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-trace/src/Service.php ( 0.77 KB )
  61. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Middleware.php ( 6.72 KB )
  62. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/initializer/BootService.php ( 0.77 KB )
  63. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/Paginator.php ( 11.86 KB )
  64. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-validate/src/Validate.php ( 63.20 KB )
  65. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/Model.php ( 23.55 KB )
  66. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/Attribute.php ( 21.05 KB )
  67. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/AutoWriteData.php ( 4.21 KB )
  68. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/Conversion.php ( 6.44 KB )
  69. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/DbConnect.php ( 5.16 KB )
  70. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/ModelEvent.php ( 2.33 KB )
  71. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/RelationShip.php ( 28.29 KB )
  72. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/contract/Arrayable.php ( 0.09 KB )
  73. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/contract/Jsonable.php ( 0.13 KB )
  74. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/contract/Modelable.php ( 0.09 KB )
  75. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Db.php ( 2.88 KB )
  76. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/DbManager.php ( 8.52 KB )
  77. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Log.php ( 6.28 KB )
  78. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Manager.php ( 3.92 KB )
  79. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/psr/log/src/LoggerTrait.php ( 2.69 KB )
  80. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/psr/log/src/LoggerInterface.php ( 2.71 KB )
  81. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Cache.php ( 4.92 KB )
  82. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/psr/simple-cache/src/CacheInterface.php ( 4.71 KB )
  83. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/helper/Arr.php ( 16.63 KB )
  84. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/cache/driver/File.php ( 7.84 KB )
  85. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/cache/Driver.php ( 9.03 KB )
  86. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php ( 1.99 KB )
  87. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/Request.php ( 0.09 KB )
  88. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Request.php ( 55.78 KB )
  89. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/middleware.php ( 0.25 KB )
  90. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Pipeline.php ( 2.61 KB )
  91. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-trace/src/TraceDebug.php ( 3.40 KB )
  92. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/middleware/SessionInit.php ( 1.94 KB )
  93. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Session.php ( 1.80 KB )
  94. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/session/driver/File.php ( 6.27 KB )
  95. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php ( 0.87 KB )
  96. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/session/Store.php ( 7.12 KB )
  97. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Route.php ( 23.73 KB )
  98. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/RuleName.php ( 5.75 KB )
  99. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/Domain.php ( 2.53 KB )
  100. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/RuleGroup.php ( 22.43 KB )
  101. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/Rule.php ( 26.95 KB )
  102. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/RuleItem.php ( 9.78 KB )
  103. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/route/app.php ( 3.94 KB )
  104. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/facade/Route.php ( 4.70 KB )
  105. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/dispatch/Controller.php ( 4.74 KB )
  106. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/Dispatch.php ( 10.44 KB )
  107. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/controller/Index.php ( 9.87 KB )
  108. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/BaseController.php ( 2.05 KB )
  109. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/facade/Db.php ( 0.93 KB )
  110. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/connector/Mysql.php ( 5.44 KB )
  111. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/PDOConnection.php ( 52.47 KB )
  112. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/Connection.php ( 8.39 KB )
  113. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/ConnectionInterface.php ( 4.57 KB )
  114. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/builder/Mysql.php ( 16.58 KB )
  115. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/Builder.php ( 24.06 KB )
  116. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/BaseBuilder.php ( 27.50 KB )
  117. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/Query.php ( 15.71 KB )
  118. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/BaseQuery.php ( 45.13 KB )
  119. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php ( 7.43 KB )
  120. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php ( 3.26 KB )
  121. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php ( 20.07 KB )
  122. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/ParamsBind.php ( 3.66 KB )
  123. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/ResultOperation.php ( 7.01 KB )
  124. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/WhereQuery.php ( 19.37 KB )
  125. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php ( 7.11 KB )
  126. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php ( 2.63 KB )
  127. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/Transaction.php ( 2.77 KB )
  128. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/log/driver/File.php ( 5.96 KB )
  129. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php ( 0.86 KB )
  130. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/log/Channel.php ( 3.89 KB )
  131. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/event/LogRecord.php ( 1.02 KB )
  132. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/Collection.php ( 16.47 KB )
  133. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/facade/View.php ( 1.70 KB )
  134. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/View.php ( 4.39 KB )
  135. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/controller/Es.php ( 3.30 KB )
  136. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Response.php ( 8.81 KB )
  137. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/response/View.php ( 3.29 KB )
  138. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Cookie.php ( 6.06 KB )
  139. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-view/src/Think.php ( 8.38 KB )
  140. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php ( 1.60 KB )
  141. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-template/src/Template.php ( 46.61 KB )
  142. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-template/src/template/driver/File.php ( 2.41 KB )
  143. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-template/src/template/contract/DriverInterface.php ( 0.86 KB )
  144. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/runtime/temp/c935550e3e8a3a4c27dd94e439343fdf.php ( 31.50 KB )
  145. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-trace/src/Html.php ( 4.42 KB )
  1. CONNECT:[ UseTime:0.000641s ] mysql:host=127.0.0.1;port=3306;dbname=wenku;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.000789s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.000318s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.000247s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.000473s ]
  6. SELECT * FROM `set` [ RunTime:0.000191s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.000482s ]
  8. SELECT * FROM `article` WHERE `id` = 663644 LIMIT 1 [ RunTime:0.000668s ]
  9. UPDATE `article` SET `lasttime` = 1779886135 WHERE `id` = 663644 [ RunTime:0.001117s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 64 LIMIT 1 [ RunTime:0.000212s ]
  11. SELECT * FROM `article` WHERE `id` < 663644 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.000430s ]
  12. SELECT * FROM `article` WHERE `id` > 663644 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.000350s ]
  13. SELECT * FROM `article` WHERE `id` < 663644 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.001121s ]
  14. SELECT * FROM `article` WHERE `id` < 663644 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.000622s ]
  15. SELECT * FROM `article` WHERE `id` < 663644 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.000729s ]
0.089562s