一、痛点:CI里Poetry慢到想砸键盘

做Python项目的人都懂,本地装依赖顺得不行,一到CI流水线(就是那种跑测试、打包的自动化流程)里,Poetry装依赖慢得像蜗牛——要么是每次都重新拉一遍所有包,要么是连包源都卡得要死,本来10分钟能跑完的流程,光装依赖就占了8分钟,急得人想直接关页面。其实慢不是Poetry本身的锅,是你没摸对优化的门道,今天就把我踩坑踩出来的三个核心优化方向(缓存、镜像、预编译包),连具体操作带坑点全说清楚。

二、优化方向1:缓存策略——别让CI每次从头来

很多人觉得CI是“干净环境”,所以每次都得重新装所有依赖,这是最大的误区。只要依赖版本没换,包的内容就不会变,完全可以把之前装过的包存起来,下次直接用,这就是缓存的核心。

2.1 缓存的核心逻辑

Poetry装依赖时,会把下载的包(包括源码包和编译好的包)存在本地的缓存目录里,同时会把项目的虚拟环境(装依赖的地方)单独存着。我们要做的,就是让CI把这两个东西存下来,下次跑的时候先判断依赖有没有变,没变就直接用缓存,不用重新装。

2.2 具体实现(以GitHub Actions为例)

这里选GitHub Actions是因为用的人最多,操作也简单,其他CI工具(比如GitLab CI、Jenkins)逻辑一样,只是配置语法不同。

2.2.1 先搞懂缓存的“判断依据”

缓存不能随便用,要是项目的依赖版本变了(比如改了pyproject.toml里的包版本),还拿旧缓存来用,肯定会出问题。所以得给缓存加个“判断规则”:只要pyproject.toml和poetry.lock这两个文件没改,就用旧缓存;改了就重新装依赖。

2.2.2 完整配置示例

先明确技术栈:GitHub Actions(CI工具)+ Poetry(Python依赖管理工具)

# 完整的GitHub Actions配置文件,名字可以叫ci.yml,放在项目的.github/workflows目录下
name: CI流水线
on:
  push:
    branches: [ main ] # 推送到main分支时触发
  pull_request:
    branches: [ main ] # 向main分支提PR时触发

jobs:
  test:
    runs-on: ubuntu-latest # 用Ubuntu系统跑流水线
    steps:
      # 第一步:拉项目代码
      - name: 拉取项目代码
        uses: actions/checkout@v4

      # 第二步:设置Python环境(Poetry依赖Python)
      - name: 设置Python环境
        uses: actions/setup-python@v5
        with:
          python-version: "3.11" # 用项目需要的Python版本

      # 第三步:安装Poetry(第一次跑的时候装,后面可以缓存)
      - name: 安装Poetry
        run: |
          curl -sSL https://install.python-poetry.org | python3 -
          echo "PATH=$HOME/.local/bin:$PATH" >> $GITHUB_ENV # 把Poetry的路径加到环境变量里,后面能直接用poetry命令

      # 第四步:缓存Poetry的核心内容(缓存目录和虚拟环境)
      - name: 缓存Poetry依赖
        uses: actions/cache@v3
        with:
          # 缓存的“判断规则”:只要pyproject.toml和poetry.lock没改,就用旧缓存
          key: poetry-cache-${{ hashFiles('pyproject.toml', 'poetry.lock') }}
          # 缓存的内容:两个核心目录
          path: |
            ~/.cache/pypoetry # Poetry自己的包缓存目录(存下载的包)
            .venv # 项目的虚拟环境目录(存装好的依赖)

      # 第五步:安装依赖(如果缓存命中,这步会跳过,直接用缓存的虚拟环境)
      - name: 安装项目依赖
        run: poetry install --no-root # --no-root是不装项目本身,只装依赖,速度更快

2.3 缓存的坑点和注意事项

  1. 缓存的判断规则必须准确:要是只判断pyproject.toml,不判断poetry.lock,可能会出现pyproject.toml里的版本范围没变,但poetry.lock里的具体版本更新了的情况,导致用旧缓存出问题;
  2. 虚拟环境的路径要对:有些项目的虚拟环境会被Poetry放在其他地方,比如~/.virtualenvs/xxx,这时候要把path改成对应的路径,不然缓存的内容不对;
  3. 不要缓存太大的东西:比如项目的node_modules(如果是前后端混合)、编译生成的临时文件,这些会让缓存体积变大,反而影响速度。

三、优化方向2:镜像源配置——别在国外源上“卡网速”

很多人装Poetry时,默认用的是国外的PyPI源(比如pypi.org),国内网络连国外源要么慢要么断,换国内的镜像源能直接把下载速度提好几倍。

3.1 镜像源的核心逻辑

PyPI是Python包的官方仓库,所有的包都存在这里,镜像源就是把官方仓库的包同步到国内的服务器,我们装包时直接从国内服务器拉,速度就快了。

3.2 具体实现(两种配置方式)

这里的技术栈还是Poetry,所以配置都是针对Poetry的。

3.2.1 全局配置(所有项目都用国内源)

适合自己开发的电脑,或者CI里所有项目都用同一个源的情况。

# 先查看Poetry当前的配置,确认镜像源有没有问题
poetry config --list
# 输出里会有repositories.pypi.url,默认是https://pypi.org/simple/

# 改成国内的镜像源(比如阿里云的PyPI源)
poetry config repositories.pypi.url https://mirrors.aliyun.com/pypi/simple/
# 也可以用清华的源:https://pypi.tuna.tsinghua.edu.cn/simple/
# 或者豆瓣的源:https://pypi.doubanio.com/simple/

# 再验证一下配置
poetry config --list | grep repositories.pypi.url
# 输出应该是你改的国内源地址

3.2.2 项目级配置(只有当前项目用国内源)

适合团队开发,每个项目的源可能不一样,或者不想影响其他项目的配置。

需要在项目的pyproject.toml文件里加一个tool.poetry.source的配置,示例:

[tool.poetry]
name = "my-project"
version = "0.1.0"
description = ""
authors = ["Your Name <your.email@example.com>"]

# 加的项目级镜像源配置
[[tool.poetry.source]]
name = "aliyun" # 源的名字,随便取
url = "https://mirrors.aliyun.com/pypi/simple/" # 国内源地址
priority = "default" # 优先级,设为default就是默认用这个源

[tool.poetry.dependencies]
python = "^3.11"
requests = "^2.31.0"
pandas = "^2.1.0"

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

改完pyproject.toml后,要重新生成poetry.lock文件,这样下次装依赖时就会用国内源了:

poetry lock --no-update # --no-update是不更新包版本,只重新生成lock文件

3.3 镜像源的坑点和注意事项

  1. 有些国内源可能同步不及时:比如有些新发布的包,国内源可能要过几个小时才会同步,这时候可以临时改回官方源,或者换一个同步快的源;
  2. 不要随便用不知名的源:有些小公司的源可能会篡改包内容,存在安全风险,尽量用大厂的源(阿里云、清华、豆瓣);
  3. 镜像源的地址要对:比如有些源的地址是https://xxx/pypi,少了后面的/simple/,就会报错,一定要确认地址正确。

四、优化方向3:预装wheel——别让编译拖速度

有些Python包(比如numpy、pandas、pillow)是C语言写的,装的时候需要编译,编译过程特别慢,还容易出问题(比如缺少编译环境)。这时候可以用wheel包——也就是提前编译好的包,直接装不用编译,速度快很多。

4.1 wheel的核心逻辑

wheel是Python包的一种分发格式,相当于把包提前编译好,打了个包,装的时候直接解压就行,不用再编译,速度比源码包快几十倍。Poetry装包时,会优先找wheel包,没有的话才会用源码包编译。

4.2 具体实现(两种预装方式)

4.2.1 让Poetry优先用wheel包(默认其实就是,但可以强化)

Poetry默认的配置就是优先用wheel包,所以只要你的镜像源里有对应包的wheel版本,Poetry就会直接装,不用编译。但有些时候,比如装特定版本的包,可能镜像源里没有wheel,这时候可以自己提前下载wheel包,放在项目里,让Poetry装的时候直接用。

4.2.2 项目里预装wheel包(适合特定场景)

比如你装的包版本很特殊,或者镜像源里没有对应的wheel,这时候可以自己下载wheel包,放在项目的某个目录里,让Poetry装的时候从这个目录找。

步骤1:先在本地电脑上下载对应包的wheel包,比如下载numpy的1.26.0版本的wheel包:

# 先查看numpy 1.26.0有哪些wheel包
pip download numpy==1.26.0 --only-binary=:all: --dest ./wheels
# --only-binary=:all: 是只下载wheel包,不下载源码包
# --dest ./wheels 是把下载的包放在项目的wheels目录里

步骤2:把wheels目录加到Poetry的源里,让Poetry装的时候先从这个目录找包,在pyproject.toml里加配置:

[[tool.poetry.source]]
name = "local-wheels"
url = "file:///path/to/your/project/wheels" # 本地目录的地址,注意前面要加file://
priority = "primary" # 优先级最高,优先用这个源的包

步骤3:重新生成poetry.lock文件:

poetry lock --no-update

这样Poetry装numpy的时候,就会直接用项目里的wheel包,不用编译。

4.3 wheel的坑点和注意事项

  1. wheel包和系统、Python版本绑定:比如你在Windows上下载的wheel包,不能在Linux上用;Python3.11的wheel包,不能在Python3.10上用,一定要下载对应环境的wheel包;
  2. 不要随便改wheel包的内容:wheel包是提前编译好的,改里面的文件可能会导致包无法使用;
  3. 不要把太大的wheel包放到项目里:比如numpy的wheel包有几十MB,要是项目里有很多这样的包,会让项目体积变大,影响拉取速度。

五、三个优化方向的组合效果

我之前做过一个Python项目,没优化的时候,CI里装依赖要8分钟,用了缓存+国内镜像源+预装wheel之后,装依赖的时间降到了1分钟以内,速度提升了7倍多。而且这三个优化方向可以组合用,效果叠加:缓存解决了重复装的问题,镜像源解决了下载慢的问题,wheel解决了编译慢的问题,三个一起用,基本能把Poetry装依赖的速度拉到最快。

六、应用场景总结

  1. 团队协作的CI流水线:多个开发者提交代码,CI频繁跑测试,用缓存能避免每次都重新装依赖,节省时间;
  2. 国内的CI环境:国内网络连国外源慢,换国内镜像源能直接提升下载速度;
  3. 包含编译型包的项目:比如用numpy、pandas、scipy的项目,预装wheel能避免编译,节省时间;
  4. 频繁修改依赖的项目:比如项目处于快速迭代期,经常改包版本,这时候缓存的判断规则要准确,避免用旧缓存。

七、技术优缺点总结

缓存策略

优点:能避免重复装依赖,速度提升明显,操作简单; 缺点:要是缓存判断规则错了,会导致依赖版本不对,出问题;缓存体积会随着项目的发展变大,需要定期清理。

镜像源配置

优点:能直接提升下载速度,操作简单,适合所有项目; 缺点:国内源可能同步不及时,不知名的源有安全风险。

预装wheel

优点:能避免编译,速度提升明显,适合编译型包; 缺点:wheel包和环境绑定,移植性差,操作相对麻烦。

八、注意事项总结

  1. 所有配置都要和项目的实际情况匹配:比如Python版本、依赖版本、CI环境,不能随便抄别人的配置;
  2. 要定期检查配置:比如镜像源的地址有没有变,缓存的判断规则有没有问题;
  3. 要测试配置的效果:比如改了配置之后,要跑一次CI,看装依赖的时间有没有降,有没有出问题;
  4. 不要过度优化:比如有些项目装依赖本来就很快,没必要花时间优化,适合自己的才是最好的。