使用 uv 构建隔离的数据管道环境:Data Engineering Zoomcamp 虚拟环境实战
【免费下载链接】data-engineering-zoomcampData Engineering Zoomcamp is a free 9-week course on building production-ready data pipelines. Join the course here 👇🏼项目地址: https://gitcode.com/GitHub_Trending/da/data-engineering-zoomcamp
在数据工程实践中,一个典型的数据管道(data pipeline)会经历"读取数据 → 转换处理 → 落地存储"的完整生命周期。本篇指南以 Data Engineering Zoomcamp 的cohorts/2027/01-docker-terraform模块为背景,讲解如何使用uv这一现代化 Python 包管理器创建隔离的虚拟环境、管理依赖并运行数据管道脚本。学完本篇,你将掌握虚拟环境的原理与必要性、uv的核心命令工作流,以及如何将本地环境无缝迁移到 Docker 容器中的完整路径。
数据管道:接收数据、输出数据的服务
数据管道是一种以数据为输入、以数据为输出的服务。以最简单的形式为例:读取一个 CSV 文件,对数据进行某种转换,然后将其存储为 PostgreSQL 数据库中的一张表。
在本模块的 workshop 中,我们将构建具备以下能力的管道:
- 从网络下载 CSV 数据
- 使用 pandas 转换和清洗数据
- 将数据加载到 PostgreSQL 中以供查询
- 分块(chunk)处理数据以应对大文件
仓库中完整的落地实现位于 pipeline 目录,包含摄取脚本、Dockerfile、docker-compose 配置与辅助脚本,后续章节会逐步对照说明。
创建第一个简单管道
首先创建一个pipeline目录,并在其中创建文件pipeline.py:
import sys print("arguments", sys.argv) day = int(sys.argv[1]) print(f"Running pipeline for day {day}")这个脚本演示了管道最基础的形态:通过命令行参数接收输入(某一天的编号),打印出运行信息。接下来引入 pandas,让管道具备真正的数据处理能力:
import pandas as pd df = pd.DataFrame({"A": [1, 2], "B": [3, 4]}) print(df.head()) df.to_parquet(f"output_day_{sys.argv[1]}.parquet")此时管道会构造一个 DataFrame、打印其内容,并把结果输出为按日期命名的 Parquet 文件——这就是"数据输入 → 转换 → 输出"的雏形。
为什么需要虚拟环境
现在管道依赖 pandas,但系统中并没有安装它。在正式进入容器(container)之前,我们希望先在本地测试脚本。最直接的方式是用pip安装:
pip install pandas pyarrow但这样做会把包全局安装到系统 Python 中。如果不同项目需要同一个包的不同版本,全局安装就会引发版本冲突。例如项目 A 需要pandas 1.x,而项目 B 需要pandas 2.x,全局共存几乎不可能。
解决方案是使用虚拟环境(virtual environment):一个相互隔离的 Python 环境,将当前项目的依赖与其他项目、以及系统 Python 彻底分隔开。虚拟环境保证:
- 依赖隔离:每个项目拥有独立的包集合与版本;
- 环境可复现:配合锁文件可以精确重建相同的运行环境;
- 系统安全:不会污染系统级 Python,也不会被系统升级破坏。
uv:更现代的 Python 包管理器
本模块采用uv——一个用 Rust 编写的高性能 Python 包与项目管理器。它比pip快得多,并且自动管理虚拟环境,省去了手动创建与激活环境的繁琐步骤。
安装 uv:
pip install uv然后初始化一个 Python 项目:
uv init --python=3.13该命令会生成两个关键文件:
pyproject.toml:项目的依赖声明文件(项目元数据 + 依赖列表);.python-version:锁定项目使用的 Python 版本。
同时 uv 会自动创建虚拟环境(.venv目录)。仓库中真实的 pyproject.toml 展示了项目化后的完整形态:
[project] name = "pipeline" version = "0.1.0" description = "Add your description here" readme = "README.md" requires-python = ">=3.13" dependencies = [ "click>=8.3.1", "pandas>=2.3.3", "psycopg2-binary>=2.9.11", "pyarrow>=22.0.0", "sqlalchemy>=2.0.44", "tqdm>=4.67.1", ] [dependency-groups] dev = [ "jupyter>=1.1.1", "pgcli>=4.3.0", ]可以看到requires-python = ">=3.13"与uv init --python=3.13一一对应,而dev依赖组则专门放 Jupyter、pgcli 这类仅用于开发调试的工具。
对比虚拟环境与系统 Python
执行下面的命令,可以直观看到两种 Python 的差异:
uv run which python # Python in the virtual environment uv run python -V which python # System Python python -Vuv run会优先使用虚拟环境中的解释器,因此两条命令输出的路径与版本号通常不同——这正是环境隔离的直观证明:uv run运行在独立环境中,不依赖也不影响系统 Python。
添加依赖并运行管道
向项目添加 pandas 与 pyarrow:
uv add pandas pyarrow该命令会把依赖写入pyproject.toml,并同步安装到虚拟环境中。随后即可运行管道:
uv run python pipeline.py 10预期的输出为:
['pipeline.py', '10']job finished successfully for day = 10
sys.argv列表中的两个元素分别对应脚本名和传入的参数10,说明管道已正确接收并处理了命令行输入。
从示例到真实工程:仓库中的完整摄取管道
示例管道演示了概念,而仓库 pipeline/ingest_data.py 给出了生产级的完整实现——将 NY 出租车数据摄取到 PostgreSQL 的脚本。它与示例管道一脉相承,但补充了大量工程细节:
- 命令行参数解析:使用
click声明式定义参数,核心参数包括--pg-user、--pg-pass、--pg-host、--pg-port(默认 5432)、--pg-db(默认ny_taxi)、--year、--month、--target-table与--chunksize(默认 100000); - 显式数据类型:通过
dtype字典为 16 个字段指定类型(如VendorID用"Int64"、金额字段用"float64"、store_and_fwd_flag用"string"),避免 pandas 在读取大文件时因列类型混合产生DtypeWarning; - 日期解析:通过
parse_dates列表将tpep_pickup_datetime与tpep_dropoff_datetime解析为时间戳; - 分块摄取:
pd.read_csv(..., iterator=True, chunksize=100000)按 10 万行一批读取数据,配合tqdm进度条逐块写入 PostgreSQL——这正是原文档强调的"处理大文件"能力的工程化体现。
分块写入的完整循环逻辑如下(与 05-data-ingestion.md 中的 Jupyter 版本一致):第一块数据用df_chunk.head(0).to_sql(..., if_exists='replace')仅创建表结构,后续每块数据用if_exists='append'追加写入。执行摄取脚本的命令为:
uv run python ingest_data.py \ --pg-user=root \ --pg-pass=root \ --pg-host=localhost \ --pg-port=5432 \ --pg-db=ny_taxi \ --target-table=yellow_taxi_trips从虚拟环境到容器:uv 保证环境一致性
虚拟环境解决的是本地开发的一致性,而生产运行还需要容器。uv 的优势在于:同一套依赖声明(pyproject.toml+uv.lock)既可以驱动本地虚拟环境,也可以驱动 Docker 镜像构建,实现"本地与容器完全一致"。
仓库 pipeline/Dockerfile 展示了这一实践:
FROM python:3.13.11-slim COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/ WORKDIR /code ENV PATH="/code/.venv/bin:$PATH" COPY pyproject.toml .python-version uv.lock ./ RUN uv sync --locked COPY ingest_data.py . ENTRYPOINT ["python", "ingest_data.py"]逐行解读:
FROM python:3.13.11-slim:使用精简版 Python 3.13 镜像,缩小镜像体积;COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/:从官方 uv 镜像复制 uv 二进制(多阶段构建模式);WORKDIR /code:设置容器内工作目录;ENV PATH="/code/.venv/bin:$PATH":把虚拟环境加入 PATH,使容器内可直接使用已安装的包;COPY pyproject.toml .python-version uv.lock ./:先复制依赖清单文件,充分利用 Docker 层缓存;RUN uv sync --locked:基于锁文件安装依赖,--locked确保安装版本与锁定版本完全一致,构建可复现;COPY ingest_data.py .:复制应用代码;ENTRYPOINT ["python", "ingest_data.py"]:容器启动时执行摄取脚本。
值得一提的是,uv init时生成的uv.lock锁文件是环境可复现的关键:它精确记录了每个依赖及其传递依赖的版本哈希,无论本地还是容器,uv sync --locked都会还原出完全相同的依赖树。在 03-dockerizing-pipeline.md 中,还给出了两种 Dockerfile 写法(pip install版与 uv 版)的对比,uv 版在构建速度与可复现性上更优。
Git 配置:防止提交生成文件
管道运行后会产生 Parquet 等二进制产物,这些文件不应进入版本控制。在.gitignore中添加:
*.parquet这样 git 会忽略所有 Parquet 文件,避免把大型二进制文件误提交到仓库中。同理,虚拟环境目录.venv/也应纳入忽略列表,因为它是可随时重建的本地产物。
小结
通过本篇实战,你完成了从零构建数据管道的完整链路:
- 理解数据管道"输入 → 转换 → 输出"的基本形态;
- 理解虚拟环境解决全局依赖冲突的核心价值;
- 掌握
uv的完整工作流:uv init→uv add→uv run; - 通过
which python对比验证环境隔离效果; - 对照 pipeline 目录了解生产级摄取脚本的分块处理与参数化设计;
- 理解 uv 锁文件如何让 Docker 镜像与本地环境保持一致;
- 通过
.gitignore管理生成文件。
这套"虚拟环境 + uv 锁文件 + Dockerfile"的组合,正是后续模块中所有数据管道项目标准化环境管理的基石。接下来可以继续阅读 Dockerizing the Pipeline 与 Running PostgreSQL with Docker,将管道真正容器化并连接数据库。
【免费下载链接】data-engineering-zoomcampData Engineering Zoomcamp is a free 9-week course on building production-ready data pipelines. Join the course here 👇🏼项目地址: https://gitcode.com/GitHub_Trending/da/data-engineering-zoomcamp
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考