Skip to content

Commit 5a9b24d

Browse files
committed
update post: uv
1 parent 0b27058 commit 5a9b24d

File tree

7 files changed

+588
-40
lines changed

7 files changed

+588
-40
lines changed

content/posts/2025-09-22_python-uv.md

Lines changed: 202 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ build-backend = "uv_build"
210210
uv run exmaple-pkg
211211
```
212212

213-
### Libraries
213+
#### Libraries
214214
一个库提供函数和对象为其他项目使用.
215215
库需要构建和分发, 例如上传到 PyPI.
216216
使用 `--lib` flag 来创建库结构.
@@ -292,8 +292,8 @@ uv init example --bare
292292
```
293293

294294

295-
## Managing dependencies
296-
### Dependency fields
295+
### Managing dependencies
296+
#### Dependency fields
297297
项目依赖由以下几个字段定义
298298
- `project.dependencies`: 发布的依赖
299299
- `project.optional-dependencies`: 发布的可选依赖
@@ -307,7 +307,7 @@ uv init example --bare
307307
uv 支持使用 `uv add``uv remove` 来定义项目依赖, 但也可以直接修改 `pyproject.toml`.
308308

309309

310-
### Adding dependencies
310+
#### Adding dependencies
311311
例如这样
312312
```bash
313313
uv add httpx
@@ -434,7 +434,7 @@ uv 支持下面几种依赖源:
434434
- [Path](https://docs.astral.sh/uv/concepts/projects/dependencies/#path): 一个本地 wheel, 源码包或项目目录
435435
- [Workspace](https://docs.astral.sh/uv/concepts/projects/dependencies/#workspace-member): 当前工作空间的成员
436436

437-
### Optional dependencies
437+
#### Optional dependencies
438438
项目作为库发布时, 使一些功能称为可选的, 从而降低默认依赖树的大小, 这种做法很常见.
439439
例如, Pandas 有额外的 excel extra 和 plot extra 包, 来避免安装 Excel 解析器和 matplotlib.
440440
除非明确说明安装, Extras 使用 `package[<extra>]` 语法指明, 例如 `pandas[plot, excel]`.
@@ -491,7 +491,7 @@ name = "torch-gpu"
491491
url = "https://download.pytorch.org/whl/cu124"
492492
```
493493

494-
### Development dependencies
494+
#### Development dependencies
495495
不同于可选依赖, 开发依赖只会在本地安装, 不会在发布到 PyPI 这样的项目依赖中生效.
496496
因此, 开发依赖不包含在 `[project]` 表中.
497497
同样地, 开发依赖也可以使用 `tool.uv.sources` 来指定源.
@@ -509,7 +509,7 @@ dev = [
509509
```
510510
`dev` 组是一个特例, 可以使用 `--dev`, `--only-dev``--no-dev` flags 来设置包含或排除依赖.
511511

512-
#### Dependency groups
512+
##### Dependency groups
513513
开发环境可以被拆分成多个组, 使用 `--group` flag.
514514
例如, 想要为开发依赖 `ruff` 加入 lint 组:
515515
```bash
@@ -531,7 +531,7 @@ uv 要求所有的依赖组之间是相互兼容的, 并且会在创建 lockfile
531531

532532
如果一组的依赖和其他组的依赖不兼容, uv 会无法解析依赖并报错.
533533

534-
#### Nesting groups
534+
##### Nesting groups
535535
一个依赖组可以包含其他依赖组, 例如:
536536
```toml
537537
[dependency-groups]
@@ -547,7 +547,7 @@ test = [
547547
]
548548
```
549549

550-
#### Default groups
550+
##### Default groups
551551
默认情况下, uv 在环境中包含一个 `dev` 依赖组.
552552
默认的组可以使用 `tool.uv.default-groups` 来设置:
553553
```toml
@@ -560,7 +560,7 @@ default-groups = ["dev", "foo"]
560560
default-groups = "all"
561561
```
562562

563-
#### `requires-python`
563+
##### `requires-python`
564564
默认情况下, 依赖组必须和项目 `requires-python` 兼容.
565565
如果一个依赖组需要不同的 Python 版本, 可以在 `[tool.uv.dependency-groups]` 里面指定 Python 版本.
566566
```toml
@@ -576,7 +576,7 @@ dev = ["pytest"]
576576
dev = {requires-python = ">=3.12"}
577577
```
578578

579-
#### Legacy `dev-dependencies`
579+
##### Legacy `dev-dependencies`
580580
`[dependency-groups]` 成为标准之前, uv 使用 `tool.uv.dev-dependencies` 字段来指定开发依赖
581581
```toml
582582
[tool.uv]
@@ -585,8 +585,136 @@ dev-dependencies = [
585585
]
586586
```
587587

588+
#### Build dependencies
589+
如果一个项目使用 [Python package](https://docs.astral.sh/uv/concepts/projects/config/#build-systems) 的结构, 它可能需要构建项目的依赖, 但无需运行. 这类依赖是 `[build-system]`, 在 `build-system.requires` 下面, 详见 [PEP 518](https://peps.python.org/pep-0518/).
588590

589-
## Using workspace
591+
例如, 如果一个项目使用 `setuptools` 作为后端构建, 它应该声明 `setuptools` 作为一个构建依赖:
592+
```toml
593+
[project]
594+
name = "pandas"
595+
version = "0.1.0"
596+
597+
[build-system]
598+
requires = ["setuptools>=42"]
599+
build-backend = "setuptools.build_meta"
600+
```
601+
默认情况下, uv 构建依赖的时候会检查 `tool.uv.sources`.
602+
例如, 使用本地版本的 `setuptools` 进行构建, 将源添加进 `tool.uv.sources`
603+
```toml
604+
[project]
605+
name = pandas
606+
version = "0.1.0"
607+
608+
[build-system]
609+
requires = ["setuptools>=42"]
610+
build-backend = "setuptools.build_meta"
611+
612+
[tool.uv.sources]
613+
setuptools = { path = "./packages/setuptools" }
614+
```
615+
当发布一个包的时候, 建议当 `tool.uv.sources` 被禁用时, 运行 `uv build --no-source` 来确保正确构建.
616+
使用其他构建工具也是一样的, 例如 `pypa/build`.
617+
618+
619+
#### Editable dependencies
620+
常规的目录安装会先构建 wheel 包, 然后将该 wheel 包安装到虚拟环境中, 这回复制所有的源文件.
621+
当包源文件被编译时, 虚拟环境中的版本将保持旧版本.
622+
623+
可编辑安装通过向虚拟环境内添加项目连接 (.pth 文件) 解决了这个问题, 该链接指示解释器直接包含源文件.
624+
可编辑安装存在一些限制, 但对于开发常见非常实用, 因为虚拟环境会始终使用包的最新修改.
625+
626+
uv 默认对工作区包采用可编辑安装模式.
627+
628+
#### Virtual dependencies
629+
uv 运行依赖项设置为"虚拟"模式, 在此模式下, 依赖项本身不会作为软件包被安装, 但其依赖关系会被正常处理.
630+
631+
默认情况下, 依赖项永远不会被视作虚拟依赖.
632+
633+
对于路径源的依赖项, 若显示设置 `tool.uv.package = false` 则可成为虚拟依赖.
634+
与在依赖项目中使用 `uv` 工作不同, 即使未声明构建系统, 该软件包仍会被构建.
635+
636+
若要将某个依赖项视位虚拟依赖, 需要在其源配置中设置 `package = false`:
637+
```toml
638+
[project]
639+
dependencies = ["bar"]
640+
641+
[tool.uv.sources]
642+
bar = { path = "../projects/bar", package = false }
643+
```
644+
如果依赖设置 `tool.uv.package = false`, 则可以在 source 通过声明 `package = true` 来覆盖.
645+
```toml
646+
[project]
647+
dependencies = ["bar"]
648+
649+
[tool.uv.source]
650+
bar = { path = "../projects/bar", package = true }
651+
```
652+
同样地, 对于工作区源的依赖项, 若显式设置 `tool.uv.package = false` 也可成为虚拟依赖.
653+
即使未声明构建系统, 该工作区成员仍会被构建.
654+
655+
对于非依赖项的工作区成员, 默认情况下可设置为虚拟模式, 例如, 若父级 `pyproject.toml` 文件配置如下:
656+
```toml
657+
[project]
658+
name = "parent"
659+
version = "1.0.0"
660+
dependencies = []
661+
662+
[tool.uv.workspace]
663+
members = ["child"]
664+
```
665+
并且 child 的 `pyproject.toml` 不含有构建系统.
666+
```toml
667+
[project]
668+
name = "child"
669+
version = "1.0.0"
670+
dependencies = ["anyio"]
671+
```
672+
那么 `child` workspace 成员将不会被安装, 但是依赖 `anyio` 会被传递.
673+
674+
相对的, 如果父级声明了对 `child` 的依赖:
675+
```toml
676+
[project]
677+
name = "parent"
678+
version = "1.0.0"
679+
dependencies = ["child"]
680+
681+
[tool.uv.sources]
682+
child = { workspace = true }
683+
684+
[tool.uv.workspace]
685+
members = ["child"]
686+
```
687+
那么 `child` 将会被构建和安装.
688+
689+
690+
#### Dependency specifiers
691+
uv 采用最初由 [PEP 508](https://peps.python.org/pep-0508/) 定义的依赖项限定符, 依赖项限定符按顺序包括以下组件:
692+
- 依赖项名称
693+
- 所需额外功能
694+
- 版本限定符
695+
- 环境标记
696+
697+
版本限定符通过逗号分割进行组合, 例如 `foo >=1.2.3,<2,!=1.4.0` 表示 "`foo` 的版本需不低于 1.2.3, 小于 2, 且不能是 1.4.0".
698+
限定符会自动补零, 因此 `foo ==2` 也会匹配 foo 2.0.0.
699+
700+
星号可以用于等号匹配的最后一位数字, 例如 `foo ==2.1.*` 将接受 2.1 系列的所有版本.
701+
类似地, `~=` 匹配最后一位数字相等或更高的版本, 例如 `foo ~=1.2` 等价于 `foo >=1.2,<2`, 而 `foo ~=1.2.3` 等价于 `foo >=1.2.3,<1.3`.
702+
703+
额外功能在名称和版本直接的方括号内用逗号分隔, 例如 `pandas[excel, plot] ==2.2`.
704+
额外功能名称之间的空格会被忽略.
705+
706+
某些依赖项仅在特定环境中需要, 例如特定的 Python 版本或系统.
707+
比如要为 `importlib.metadata` 模块安装 importlib-metadata 回溯包, 可使用 `importlib-metadata >=7.1.0,<8; python_version < '3.10'`.
708+
要在 Windows 上安装 colorama 则可使用 `colorama >=0.4.6,<5; platform_systemm == "Windows`.
709+
710+
环境标记通过 and, or 和括号进行组合, 例如
711+
```
712+
aiohttp >=3.7.4,<4; (sys_paltform != 'win32' or implementation_name != 'pypy') and python_version >= '3.10'
713+
```
714+
注意标记内的版本需要加引号, 而标记外的版本不加引号.
715+
716+
717+
### Using workspace
590718
工作空间受到 Cargo 的同名概念启发, 即将一个或多个包一起管理, 叫做 workspace.
591719

592720
Workspace 通过将项目拆分多个包和相同的依赖, 从而组织大型代码库.
@@ -618,7 +746,7 @@ exclude = ["packages/seeds"]
618746
例如上例中, `uv run``uv run --package albatross` 是等效的, 而 `uv run --package bird-feeder` 则会在 `bird-feeder` 包中执行命令.
619747

620748

621-
### Workspace sources
749+
#### Workspace sources
622750
在一个 workspace 内, 对工作区成员的依赖通过 `tool.uv.sources` 实现, 例如:
623751
```
624752
[project]
@@ -642,3 +770,64 @@ requires = ["uv_build>=0.8.20,<0.9.0"]
642770
工作区根目录中的任何 `tool.uv.sources` 定义将适用于所有成员, 除非在特定成员的 `tool.uv.sources` 中被覆盖.
643771

644772

773+
#### Workspace layouts
774+
最常见的工作区布局可以是一个根目录和一系列的库组成.
775+
例如, 接着上面的例子, 工作区间有一个根目录 `albatross`, 以及 `packages` 目录下面的两个库 `bird-feeder``seed`.
776+
```
777+
albatross
778+
├── packages
779+
│ ├── bird-feeder
780+
│ │ ├── pyproject.toml
781+
│ │ └── src
782+
│ │ └── bird_feeder
783+
│ │ ├── __init__.py
784+
│ │ └── foo.py
785+
│ └── seeds
786+
│ ├── pyproject.toml
787+
│ └── src
788+
│ └── seeds
789+
│ ├── __init__.py
790+
│ └── bar.py
791+
├── pyproject.toml
792+
├── README.md
793+
├── uv.lock
794+
└── src
795+
└── albatross
796+
└── main.py
797+
```
798+
由于 `seeds``pyproject.toml` 排除在外, 故该工作区一共有两个成员: `albatross``bird-feeder`.
799+
800+
801+
#### When (not) to use workspaces
802+
Workspaces 是为了促进同一仓库下的多个包之间内部连接的开发.
803+
当代码库逐渐变复杂, 将其切分成更小的可组合的包将十分有用.
804+
每个包都有自己的依赖和版本限制.
805+
806+
工作区有助于执行隔离和分离问题.
807+
例如, 在 uv 中, 有分离的 core 包和命令行界面, 这使得我们可以分开测试 core 包和 CLI, 反之亦然.
808+
809+
其他常见工作区间使用包括:
810+
- 在模块中实现了性能关键的扩展库
811+
- 插件系统库, 每个插件是一个分离的 workspace 包和一个根目录下的依赖
812+
813+
工作区模式不适用于成员之间存在需求冲突, 或需要为每个成员创建独立虚拟环境的场景.
814+
此类情况下, 路径依赖通常是更优选择.
815+
例如, 无需将 albatross 及其相关组件强制归入同一工作区, 可以将每个软件包定义为独立项目, 并通过在 `tool.uv.sources` 中配置路径依赖来定义包间依赖关系.
816+
```toml
817+
[project]
818+
name = "albatross"
819+
version = "0.1.0"
820+
requires-python = ">=3.12"
821+
dependencies = ["bird-feeder", "tqdm>=4,<5"]
822+
823+
[tool.uv.sources]
824+
bird-feeder = { path = "packages/bird-feeder" }
825+
826+
[build-system]
827+
requires = ["uv_build>=0.8.20,<0.9.0"]
828+
build-backend = "uv_build"
829+
```
830+
这种方法能带来许多相同优势, 同时运行依赖解析和虚拟环境管理进行更精细的控制 (但无法使用 `uv run --package` 命令, 需要从对应软件目录运行命令).
831+
832+
最后, uv 的工作区强制要求整个工作区间采用统一的 `requires-python` 配置, 该配置取所有 `requires-python` 值的交集.
833+
如果需要给某个成员测试工作区其他成员不支持的 Python 版本, 可能需要使用 `uv pip` 将该成员安装到独立的虚拟环境中.

public/archives/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
posts</p></div></div><div class="relative mb-8"><div class="relative mb-4 flex items-center"><div class="bg-accent border-background absolute left-2 z-10 h-4 w-4 rounded-full border-2"></div><div class=ml-12><h3 class="text-foreground text-lg font-semibold">September 2025</h3><p class="text-muted-foreground text-xs">18
5757
posts</p></div></div><div class="ml-12 space-y-3"><article class="group bg-card border-border hover:bg-accent/50 rounded-lg border p-4 transition-all duration-300"><div class="flex items-center justify-between gap-4"><div class="min-w-0 flex-1"><h4 class="text-foreground group-hover:text-primary mb-3 font-medium transition-colors duration-200"><a href=/posts/uv-python-package-manager/ class=block>uv - Python package manager</a></h4><div class="text-muted-foreground flex items-center gap-4 text-xs"><div class="flex items-center gap-1"><svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5A2 2 0 003 7v12a2 2 0 002 2z"/></svg>
5858
<time datetime=2025-09-22>09-22</time></div><div class="flex items-center gap-1"><svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3A9 9 0 113 12a9 9 0 0118 0z"/></svg>
59-
<span>10
59+
<span>14
6060
min</span></div></div></div></div></article><article class="group bg-card border-border hover:bg-accent/50 rounded-lg border p-4 transition-all duration-300"><div class="flex items-center justify-between gap-4"><div class="min-w-0 flex-1"><h4 class="text-foreground group-hover:text-primary mb-3 font-medium transition-colors duration-200"><a href=/posts/redis-bitmap/ class=block>Redis Bitmap</a></h4><div class="text-muted-foreground flex items-center gap-4 text-xs"><div class="flex items-center gap-1"><svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5A2 2 0 003 7v12a2 2 0 002 2z"/></svg>
6161
<time datetime=2025-09-19>09-19</time></div><div class="flex items-center gap-1"><svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3A9 9 0 113 12a9 9 0 0118 0z"/></svg>
6262
<span>10

0 commit comments

Comments
 (0)