假设你有一台Mac,系统默认搭载Python 3.9.6。现在你需要搭建一个Python 3.12的开发环境,但又担心安装新版本会影响系统工具对默认Python的依赖关系。
又或者,你需要同时维护多个项目,每个项目依赖不同的Python版本,日常工作需要频繁在这些项目之间切换。
面对这类问题,全局安装Python显然不是理想方案。解决方法有很多,比如pyenv、conda、uv等工具。本文提供另一种思路——使用容器来搭建开发环境。
宿主机 (Mac或Windows或Linux)
├── /Users/<username>/Depots/my-project (源代码目录)
├── 容器1: Python开发环境 (Ubuntu 24.04)
│ ├── Python工具链
│ ├── Jupyter Notebook
│ ├── 挂载源代码目录
│ ├── 暴露端口: 8888(Jupyter Notebook), 5678(调试端口)
│ └── 连接到Postgres容器
└── 容器2: PostgreSQL数据库
└── 暴露端口: 5432需要说明的是,容器并非解决此类问题的唯一方案,也未必是最优方案。这里只是提供一种思路,因为容器具有以下独特优势:
- 环境一致性:轻松保持开发、测试、生产环境的一致性
- 高度隔离性:项目间完全独立。当你切换环境时,可以同时切换Python版本、依赖库、数据库,甚至包括HOME目录下的配置文件(如
.config) - 系统环境保护:无需在宿主机上安装大量软件。容器相当于一个独立的操作系统,删除容器即可清除所有安装的软件
- 环境可复现:更换电脑时无需重新安装大量软件,只需重新构建容器即可获得完全相同的环境,甚至可以直接导出导入容器镜像
- 团队协作优势:无需手把手帮同事配置环境,也彻底解决了"在我电脑上能跑"的经典问题
# 使用Homebrew安装
brew install podman podman-compose
# 初始化Podman虚拟机(Mac需要Linux VM运行容器)
podman machine init --cpus 4 --memory 4096 --disk-size 50
# 启动Podman虚拟机
podman machine start
# 验证安装
podman --version# 方法1:使用winget(推荐)
winget install RedHat.Podman
# 方法2:从官网下载安装器
# https://github.com/containers/podman/releases
# 即使安装了Podman Desktop,命令行使用podman-compose还需要:
pip install podman-compose
# 安装后,初始化Podman虚拟机
podman machine init --cpus 4 --memory 4096 --disk-size 50
# 启动Podman虚拟机
podman machine start
# 验证安装
podman --version说明:
- Podman是Docker的替代方案,开源免费,且无需root权限即可运行
- 分配4核CPU和4GB内存可确保开发环境流畅运行
- Windows系统需要提前启用WSL 2(推荐)或Hyper-V作为虚拟化技术的底层实现
- Windows系统还需要安装全局Python环境来运行podman-compose
在工作目录下执行以下命令(Mac使用Terminal,Windows使用PowerShell):
# 创建项目根目录
mkdir python-dev-env
cd python-dev-env
# 创建子目录
mkdir -p src
mkdir -p postgres-data
mkdir -p notebooks
mkdir -p data
mkdir -p .vscode
# 创建必要的文件
touch Dockerfile
touch docker-compose.yml
touch start.sh
touch requirements.txt
touch src/test_db.py
touch src/debug_example.py
touch notebooks/01_database_test.ipynb
touch .vscode/launch.json目录结构:
python-dev-env/
├── Dockerfile
├── docker-compose.yml
├── requirements.txt
├── start.sh # 启动脚本
├── src/ # Python脚本
│ ├── test_db.py
│ └── debug_example.py
├── notebooks/ # Jupyter notebooks
│ └── 01_database_test.ipynb
├── data/ # 数据文件
├── postgres-data/ # PostgreSQL数据
└── .vscode/
└── launch.json
编辑Dockerfile文件,添加以下内容:
# 基于Ubuntu 24.04
FROM ubuntu:24.04
# 设置环境变量,避免交互式提示
ENV DEBIAN_FRONTEND=noninteractive
ENV PYTHONUNBUFFERED=1
ENV TZ=Asia/Shanghai
# 更新系统并安装必要的工具和Python 3.12
RUN apt-get update && apt-get install -y \
software-properties-common \
curl \
wget \
git \
vim \
build-essential \
libpq-dev \
tzdata \
nodejs \
npm \
python3.12 \
python3.12-dev \
python3.12-venv \
python3-pip \
python3-pip-whl \
&& rm -rf /var/lib/apt/lists/*
# 配置系统时区
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && \
echo $TZ > /etc/timezone
# 设置Python 3.12为默认版本
RUN update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.12 1 && \
update-alternatives --install /usr/bin/python python /usr/bin/python3.12 1
# 创建工作目录
WORKDIR /workspace
# 复制requirements.txt并安装依赖
COPY requirements.txt .
# 使用系统pip安装Python包(避免升级pip本身导致冲突)
RUN python3 -m pip install --break-system-packages -r requirements.txt
# 安装debugpy(VS Code远程调试需要)
RUN python3 -m pip install --break-system-packages debugpy
# 配置Jupyter
# 创建Jupyter配置目录
RUN mkdir -p /root/.jupyter
# 生成Jupyter配置文件
RUN jupyter notebook --generate-config
# 配置Jupyter允许远程访问
RUN echo "c.ServerApp.ip = '0.0.0.0'" >> /root/.jupyter/jupyter_notebook_config.py && \
echo "c.ServerApp.allow_root = True" >> /root/.jupyter/jupyter_notebook_config.py && \
echo "c.ServerApp.open_browser = False" >> /root/.jupyter/jupyter_notebook_config.py && \
echo "c.ServerApp.token = ''" >> /root/.jupyter/jupyter_notebook_config.py && \
echo "c.ServerApp.password = ''" >> /root/.jupyter/jupyter_notebook_config.py && \
echo "c.ServerApp.port = 8888" >> /root/.jupyter/jupyter_notebook_config.py
# 暴露端口
EXPOSE 5678
EXPOSE 8888
# 启动脚本
COPY start.sh /start.sh
RUN chmod +x /start.sh
# 使用启动脚本
CMD ["/start.sh"]
说明:
- 使用Ubuntu 24.04作为基础镜像
- 安装
libpq-dev以支持PostgreSQL客户端 - 安装
debugpy用于VS Code远程调试 - 暴露5678端口供调试器连接
- 暴露8888端口供宿主机直接访问Jupyter Notebook
编辑start.sh文件:
#!/bin/bash
# 容器启动脚本:同时运行Jupyter和保持容器活跃
echo "======================================"
echo "启动Python开发环境"
echo "======================================"
# 启动Jupyter Notebook(后台运行)
echo "正在启动Jupyter Notebook..."
jupyter notebook --ip=0.0.0.0 --port=8888 --no-browser --allow-root \
--notebook-dir=/workspace \
> /var/log/jupyter.log 2>&1 &
# 等待Jupyter启动
sleep 3
# 显示Jupyter访问信息
echo ""
echo "======================================"
echo "✓ Jupyter Notebook已启动"
echo "======================================"
echo "访问地址: http://localhost:8888"
echo "工作目录: /workspace"
echo ""
echo "日志位置: /var/log/jupyter.log"
echo "======================================"
echo ""
# 保持容器运行
tail -f /dev/null说明:
- 自动启动Jupyter Notebook
- 在后台运行,不阻塞容器
- 显示访问信息
- 保持容器运行状态
编辑requirements.txt,添加Python依赖:
# 数据库相关
psycopg2-binary==2.9.9
python-dotenv==1.0.0
# 调试工具
debugpy==1.8.0
# Jupyter相关
jupyter==1.0.0
jupyterlab==4.0.9
notebook==7.0.6
ipykernel==6.27.1
ipywidgets==8.1.1
# 数据分析常用库(可选但推荐)
pandas==2.1.4
numpy==1.26.2
matplotlib==3.8.2
seaborn==0.13.0
plotly==5.18.0
# SQL查询工具(在Jupyter中更方便地查询数据库)
ipython-sql==0.5.0
sqlalchemy==2.0.23
编辑docker-compose.yml(Podman也支持docker-compose格式):
version: '3.8'
services:
# Python开发容器(含Jupyter)
python-dev:
build: .
container_name: python-dev-jupyter
hostname: python-dev
volumes:
- ./src:/workspace/src:z
- ./notebooks:/workspace/notebooks:z
- ./data:/workspace/data:z
ports:
- "5678:5678" # debugpy调试端口
- "8888:8888" # Jupyter端口
depends_on:
postgres:
condition: service_healthy
networks:
- python-dev-network
stdin_open: true
tty: true
environment:
- JUPYTER_ENABLE_LAB=yes
- PYTHONUNBUFFERED=1
- TZ=Asia/Shanghai
restart: unless-stopped
# PostgreSQL数据库容器
postgres:
image: postgres:16
container_name: postgres-pydev
hostname: postgres-pydev
environment:
POSTGRES_USER: devuser
POSTGRES_PASSWORD: devpassword
POSTGRES_DB: devdb
TZ: Asia/Shanghai
PGTZ: Asia/Shanghai
volumes:
- ./postgres-data:/var/lib/postgresql/data:z
ports:
- "5432:5432"
networks:
- python-dev-network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U devuser"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
networks:
python-dev-network:
driver: bridge配置说明:
- 两个服务:
python-dev(开发容器)和postgres(数据库) - volumes挂载:
./src:/workspace/src:本地代码实时同步到容器./postgres-data:数据库数据持久化到本地:z标志:在SELinux环境下正确设置权限(Mac/Windows会自动忽略)
- 网络配置:两个容器位于同一网络中,可通过容器名互相访问
- 端口映射:
- 5678:调试端口
- 5432:数据库端口(可选,允许宿主机直接访问数据库)
- 8888:供浏览器访问Jupyter
- depends_on:确保postgres容器先启动
- healthcheck:检查数据库是否就绪
在项目根目录执行:
# 构建镜像(首次运行或Dockerfile修改后需要)
podman-compose build
# 启动所有容器
podman-compose up -d
# 查看容器状态
podman ps
# 查看日志(如果有问题)
podman-compose logs命令说明:
build:根据Dockerfile构建Python开发镜像up -d:后台启动所有服务ps:查看运行中的容器logs:查看容器日志,用于排查启动问题
编辑src/test_db.py:
#!/usr/bin/env python3
"""
数据库连接测试脚本
验证Python环境和PostgreSQL连接是否正常
"""
import sys
import psycopg2
from psycopg2 import sql
from datetime import datetime
def print_python_version():
"""打印Python版本信息"""
print("=" * 50)
print("Python环境信息")
print("=" * 50)
print(f"Python版本: {sys.version}")
print(f"Python路径: {sys.executable}")
print()
def test_database_connection():
"""测试数据库连接"""
print("=" * 50)
print("PostgreSQL连接测试")
print("=" * 50)
# 数据库连接参数
# 注意:这里使用容器名'postgres'作为主机名
conn_params = {
'host': 'postgres', # docker-compose中的服务名
'port': 5432,
'database': 'devdb',
'user': 'devuser',
'password': 'devpassword'
}
try:
# 连接数据库
print("正在连接数据库...")
conn = psycopg2.connect(**conn_params)
print("✓ 数据库连接成功!")
# 创建游标
cursor = conn.cursor()
# 获取PostgreSQL版本
cursor.execute("SELECT version();")
db_version = cursor.fetchone()[0]
print(f"\n数据库版本:\n{db_version}")
# 创建测试表
print("\n正在创建测试表...")
cursor.execute("""
DROP TABLE IF EXISTS test_table;
CREATE TABLE test_table (
id SERIAL PRIMARY KEY,
message VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
""")
conn.commit()
print("✓ 测试表创建成功!")
# 插入测试数据
print("\n正在插入测试数据...")
test_messages = [
"Hello from Python 3.13!",
"PostgreSQL 16 is working!",
"Podman environment is ready!"
]
for msg in test_messages:
cursor.execute(
"INSERT INTO test_table (message) VALUES (%s)",
(msg,)
)
conn.commit()
print(f"✓ 成功插入 {len(test_messages)} 条记录!")
# 查询并显示数据
print("\n查询结果:")
print("-" * 50)
cursor.execute("SELECT id, message, created_at FROM test_table ORDER BY id;")
rows = cursor.fetchall()
for row in rows:
print(f"ID: {row[0]}")
print(f"消息: {row[1]}")
print(f"创建时间: {row[2]}")
print("-" * 50)
# 清理测试表
cursor.execute("DROP TABLE test_table;")
conn.commit()
print("\n✓ 测试完成,清理测试表成功!")
# 关闭连接
cursor.close()
conn.close()
print("✓ 数据库连接已关闭")
return True
except psycopg2.Error as e:
print(f"✗ 数据库错误: {e}")
return False
except Exception as e:
print(f"✗ 发生错误: {e}")
return False
def main():
"""主函数"""
print("\n" + "=" * 50)
print("开发环境验证脚本")
print("=" * 50 + "\n")
# 测试Python版本
print_python_version()
# 测试数据库连接
success = test_database_connection()
# 总结
print("\n" + "=" * 50)
if success:
print("✓ 所有测试通过!环境配置成功!")
else:
print("✗ 测试失败,请检查配置")
print("=" * 50 + "\n")
return 0 if success else 1
if __name__ == "__main__":
sys.exit(main())脚本说明:
- 验证Python 3.12是否正确安装
- 测试PostgreSQL连接(使用容器名
postgres作为host) - 执行基本的CRUD操作,确保数据库正常工作
- 提供清晰的输出,便于排查问题
# 进入Python开发容器
podman exec -it python-dev-jupyter bash
# 在容器内运行测试脚本
cd /workspace
python src/test_db.py
# 退出容器
exit预期输出:
==================================================
开发环境验证脚本
==================================================
==================================================
Python环境信息
==================================================
Python版本: 3.12.x ...
Python路径: /usr/bin/python
==================================================
PostgreSQL连接测试
==================================================
正在连接数据库...
✓ 数据库连接成功!
数据库版本:
PostgreSQL 16.x ...
...
==================================================
✓ 所有测试通过!环境配置成功!
==================================================
在浏览器中打开:
http://localhost:8888
你会看到JupyterLab界面。如果显示经典Notebook界面,可以在URL中添加/lab切换到Lab模式。
用宿主机上的VS Code打开notebooks/01_database_test.ipynb,依次插入以下代码片段并保存:
# 导入必要的库
import psycopg2
import pandas as pd
from datetime import datetime
import warnings
warnings.filterwarnings('ignore')
print("✓ 库导入成功")
print(f"Python版本: {psycopg2.extensions.libpq_version()}")# 数据库连接参数
conn_params = {
'host': 'postgres',
'port': 5432,
'database': 'devdb',
'user': 'devuser',
'password': 'devpassword'
}
try:
conn = psycopg2.connect(**conn_params)
print("✓ 数据库连接成功!")
# 获取数据库版本
cursor = conn.cursor()
cursor.execute("SELECT version();")
version = cursor.fetchone()[0]
print(f"\n数据库版本:\n{version}")
cursor.close()
except Exception as e:
print(f"✗ 连接失败: {e}")cursor = conn.cursor()
# 创建示例表
cursor.execute("""
DROP TABLE IF EXISTS sales_data;
CREATE TABLE sales_data (
id SERIAL PRIMARY KEY,
product_name VARCHAR(100),
category VARCHAR(50),
price DECIMAL(10, 2),
quantity INTEGER,
sale_date DATE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
""")
# 插入示例数据
sample_data = [
('笔记本电脑', '电子产品', 5999.00, 3, '2024-01-15'),
('无线鼠标', '电子产品', 129.00, 10, '2024-01-16'),
('机械键盘', '电子产品', 599.00, 5, '2024-01-17'),
('显示器', '电子产品', 1999.00, 2, '2024-01-18'),
('USB-C数据线', '配件', 49.00, 20, '2024-01-19'),
('笔记本支架', '配件', 89.00, 8, '2024-01-20'),
]
for data in sample_data:
cursor.execute("""
INSERT INTO sales_data (product_name, category, price, quantity, sale_date)
VALUES (%s, %s, %s, %s, %s)
""", data)
conn.commit()
print(f"✓ 成功创建表并插入 {len(sample_data)} 条数据")
cursor.close()# 使用pandas读取数据
query = "SELECT * FROM sales_data ORDER BY sale_date"
df = pd.read_sql_query(query, conn)
print("数据概览:")
print(df.head())
print("\n数据统计:")
print(df.describe())import matplotlib.pyplot as plt
import seaborn as sns
# 设置中文字体(解决中文显示问题)
plt.rcParams['font.sans-serif'] = ['DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
# 按类别统计销售额
df['total_sales'] = df['price'] * df['quantity']
category_sales = df.groupby('category')['total_sales'].sum().sort_values(ascending=False)
# 创建图表
fig, axes = plt.subplots(1, 2, figsize=(15, 5))
# 柱状图:按类别的销售额
category_sales.plot(kind='bar', ax=axes[0], color='skyblue')
axes[0].set_title('Sales by Category', fontsize=14, fontweight='bold')
axes[0].set_xlabel('Category')
axes[0].set_ylabel('Total Sales (CNY)')
axes[0].tick_params(axis='x', rotation=45)
# 饼图:销售数量分布
df.groupby('category')['quantity'].sum().plot(
kind='pie',
ax=axes[1],
autopct='%1.1f%%',
startangle=90
)
axes[1].set_title('Quantity Distribution by Category', fontsize=14, fontweight='bold')
axes[1].set_ylabel('')
plt.tight_layout()
plt.show()
print("\n各类别销售统计:")
print(category_sales)在宿主机的浏览器中打开http://localhost:8888,在Jupyter界面中打开notebooks/01_database_test.ipynb,依次执行5个cell中的代码片段。
预期输出:
✓ 库导入成功
Python版本: ...
✓ 数据库连接成功!
数据库版本:
PostgreSQL 16.x ...
✓ 成功创建表并插入 6 条数据
...
(柱状图和饼图)
在VS Code中安装以下扩展:
- Python (Microsoft)
- Dev Containers (Microsoft)
- Pylance (Microsoft)
编辑.vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"name": "Python: Remote Attach (container)",
"type": "python",
"request": "attach",
"connect": { "host": "localhost", "port": 5678 },
"pathMappings": [
{
"localRoot": "${workspaceFolder}/src",
"remoteRoot": "/workspace/src"
}
],
"justMyCode": false
}
]
}配置说明:
- Remote Attach:连接到容器内运行的debugpy服务器
- pathMappings:映射本地路径和容器路径,确保断点正确命中
- port 5678:debugpy默认端口
编辑src/debug_example.py:
#!/usr/bin/env python3
"""
调试示例脚本
演示如何使用VS Code进行远程调试
"""
import psycopg2
def connect_database():
"""连接数据库示例函数"""
conn = psycopg2.connect(
host='postgres',
port=5432,
database='devdb',
user='devuser',
password='devpassword'
)
return conn
def main():
print("开始调试示例程序...")
# 在这里设置断点进行调试
conn = connect_database()
cursor = conn.cursor()
# 执行查询
cursor.execute("SELECT current_database(), current_user;")
result = cursor.fetchone()
print(f"当前数据库: {result[0]}")
print(f"当前用户: {result[1]}")
cursor.close()
conn.close()
print("程序执行完成!")
if __name__ == "__main__":
main()-
在宿主机上打开终端,运行:
# 在容器中运行你的脚本,并让 debugpy 监听 5678 端口 podman exec -it python-dev-jupyter \ python -m debugpy --listen 0.0.0.0:5678 --wait-for-client \ /workspace/src/debug_example.py
说明:
- 使用debugpy启动远程调试器,并等待VS Code连接
- 在程序结束前,请勿关闭此终端窗口
-
在宿主机的VS Code中打开
src/debug_example.py -
在需要调试的代码行设置断点(点击行号左侧)
-
按
F5或点击"运行和调试" -
等待程序运行并命中断点
注意:这种方式下,VS Code会连接到容器内执行的Python进程进行调试。
