Skip to content

Virtual Environments — Cô lập Môi trường Python

Thứ Sáu, 17h45. Deployment pipeline chạy xanh lè trên staging. Team merge vào main, trigger production deploy. 10 phút sau — toàn bộ API trả về HTTP 500. Root cause mất 3 tiếng để tìm ra: cryptography==41.0.0 vừa release, và requirements.txt ghi cryptography>=38.0 thay vì pin chính xác version. Staging cài 40.x tuần trước, production cài 41.0 vừa release sáng nay. Một ký tự >= thay vì ==, toàn bộ hệ thống sụp đổ cuối tuần.

Đây là "dependency hell" — không phải lý thuyết, mà là thực tế xảy ra hàng tuần ở các team không kiểm soát môi trường phát triển. Vấn đề không chỉ là version conflict giữa hai project trên cùng máy; vấn đề sâu hơn là tính tái tạo (reproducibility). Nếu bạn không thể đảm bảo rằng máy của đồng nghiệp, CI server, và production chạy chính xác cùng dependency tree, bạn đang chơi xổ số mỗi lần deploy.

Virtual environment là lớp phòng thủ đầu tiên. Mỗi project có sandbox riêng — Python interpreter riêng, site-packages riêng, dependency tree riêng. Kết hợp với lock file và tool hiện đại, bạn biến "works on my machine" thành "works on every machine, every time". Insight nhanh: python -m venv .venv là lệnh đầu tiên bạn nên chạy khi bắt đầu bất kỳ project Python nào — trước cả khi viết dòng code đầu tiên.


Bức tranh tư duy

Hãy hình dung system Python như bếp chung của một khu nhà trọ. Tất cả mọi người dùng chung tủ lạnh, chung gia vị, chung xoong nồi. Anh A mua dầu ăn hiệu X, anh B đổ đi thay bằng hiệu Y vì món của anh cần loại khác. Anh C cần muối hạt thô, nhưng anh D đã thay bằng muối bột. Mỗi người nấu xong thì bếp lại ở trạng thái khác — không ai kiểm soát được.

Virtual environment chính là bếp riêng cho từng đầu bếp. Mỗi project có tủ lạnh riêng, gia vị riêng, dụng cụ riêng. Anh A muốn requests==2.25? Để trong bếp anh A. Anh B cần requests==2.31? Để trong bếp anh B. Không ai đụng vào bếp của ai. Khi cần dọn dẹp, xóa cả căn bếp — không ảnh hưởng ai khác.

┌──────────────────────────────────────────────────────┐
│  System Python (/usr/bin/python3)                    │
│  └── site-packages/   ← BẾP CHUNG (nguy hiểm!)     │
│      ├── requests==2.25     ← Ai cài? Lúc nào?      │
│      └── numpy==1.19        ← Project nào cần?       │
├──────────────────────────────────────────────────────┤
│  Project A (.venv_a/)          BẾP RIÊNG A           │
│  ├── bin/python3               Python riêng           │
│  └── lib/site-packages/                              │
│      ├── requests==2.25.1  ✅  Đúng version A cần    │
│      └── flask==3.0.0      ✅  Chỉ A dùng            │
├──────────────────────────────────────────────────────┤
│  Project B (.venv_b/)          BẾP RIÊNG B           │
│  ├── bin/python3               Python riêng           │
│  └── lib/site-packages/                              │
│      ├── requests==2.31.0  ✅  Đúng version B cần    │
│      └── django==5.0       ✅  Chỉ B dùng            │
└──────────────────────────────────────────────────────┘

Khi nào analogy bị breakdown: Bếp thật tốn tiền xây. Virtual environment gần như không tốn chi phí — một thư mục vài chục MB, tạo trong vài giây, xóa bằng rm -rf. Ngoài ra, bếp thật cách ly hoàn toàn — nhưng venv vẫn dùng chung kernel Python interpreter (thông qua symlink), chỉ cách ly site-packages. Để cách ly hoàn toàn đến mức OS, bạn cần Docker.


Cốt lõi kỹ thuật

venv — Built-in từ Python 3.3+

venv là module chuẩn của Python, không cần cài thêm gì. Đây là lựa chọn mặc định cho hầu hết project.

bash
# Tạo virtual environment
python -m venv .venv

# Activate — Linux/macOS
source .venv/bin/activate

# Activate — Windows CMD
.venv\Scripts\activate.bat

# Activate — Windows PowerShell
.venv\Scripts\Activate.ps1

# Kiểm tra activate thành công
which python    # → .venv/bin/python

# Deactivate khi xong
deactivate

# Xóa hoàn toàn — chỉ cần xóa thư mục
rm -rf .venv

Kiểm tra bằng Python:

python
import sys

print(sys.prefix)       # /path/to/project/.venv
print(sys.base_prefix)  # /usr/local (system Python gốc)
print(sys.executable)   # /path/to/project/.venv/bin/python

# Nếu sys.prefix != sys.base_prefix → đang trong venv
def is_inside_venv() -> bool:
    return sys.prefix != sys.base_prefix

virtualenv — Nhanh hơn, linh hoạt hơn

virtualenv là phiên bản nâng cao của venv, tạo environment nhanh hơn nhờ caching và hỗ trợ nhiều Python version.

bash
pip install virtualenv

# Tạo environment (nhanh hơn venv nhờ seed cache)
virtualenv .venv

# Chỉ định Python version cụ thể
virtualenv -p python3.11 .venv

# Kế thừa system packages (dùng khi cần package đã cài sẵn ở system)
virtualenv --system-site-packages .venv

Dùng virtualenv khi: cần tốc độ tạo environment (CI/CD pipeline), cần Python version khác system, hoặc cần --system-site-packages cho scientific computing.

conda — Cho Data Science và ML

conda quản lý không chỉ Python packages mà cả native libraries (CUDA, cuDNN, OpenCV, GDAL). Đây là lựa chọn chuẩn cho Machine Learning.

bash
# Tạo environment với Python version cụ thể
conda create -n ml-project python=3.12

# Activate
conda activate ml-project

# Cài packages từ conda-forge
conda install -c conda-forge numpy pandas scikit-learn

# Export environment (reproducible)
conda env export > environment.yml

# Tái tạo từ file
conda env create -f environment.yml

# Deactivate và xóa
conda deactivate
conda env remove -n ml-project

Lưu ý quan trọng: Khi dùng conda, hạn chế tối đa pip install bên trong conda env. Mixing conda và pip thường gây conflict metadata. Nếu bắt buộc dùng pip, cài tất cả conda packages trước, rồi mới pip install phần còn lại.

Poetry — Quản lý toàn diện

Poetry kết hợp virtual environment management, dependency resolution, và build/publish trong một tool duy nhất.

bash
# Khởi tạo project mới
poetry new my-service
cd my-service

# Hoặc init trong project có sẵn
poetry init

# Thêm dependencies
poetry add requests httpx
poetry add pytest ruff --group dev

# Install toàn bộ (tạo venv tự động)
poetry install

# Chạy trong venv mà không cần activate
poetry run python main.py

# Lock dependencies (tạo poetry.lock)
poetry lock

# Export cho production (không cần Poetry trên server)
poetry export -f requirements.txt -o requirements.txt
toml
# pyproject.toml — Poetry quản lý
[tool.poetry]
name = "my-service"
version = "0.1.0"
description = "Production API service"

[tool.poetry.dependencies]
python = "^3.12"
requests = "^2.31.0"
httpx = "^0.27.0"

[tool.poetry.group.dev.dependencies]
pytest = "^8.0"
ruff = "^0.4"

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

uv — Thế hệ mới, tốc độ Rust

uv (từ Astral, team phát triển Ruff) là package manager viết bằng Rust, nhanh hơn pip 10-100 lần. Đây là hướng đi tương lai của Python packaging.

bash
# Cài đặt uv
curl -LsSf https://astral.sh/uv/install.sh | sh

# Khởi tạo project (tạo pyproject.toml + .venv)
uv init my-service
cd my-service

# Thêm dependencies
uv add requests httpx
uv add pytest ruff --dev

# Lock và sync (tạo uv.lock, cài vào .venv)
uv lock
uv sync

# Chạy script trong venv
uv run python main.py

# Tạo venv thủ công (nếu cần)
uv venv --python 3.12

uv là lựa chọn được khuyến nghị cho project mới trong năm 2024-2025 nhờ tốc độ vượt trội và khả năng thay thế hoàn toàn pip, pip-tools, và virtualenv.

Bảng so sánh tổng quan

Tiêu chívenvvirtualenvcondaPoetryuv
Cài đặtBuilt-inpip installStandalonepip/scriptStandalone
Tốc độ tạo envTrung bìnhNhanhChậmTrung bìnhRất nhanh
Lock fileenvironment.ymlpoetry.lockuv.lock
Quản lý Python versionHạn chế
Native libs (C/CUDA)
Build & publish
pyproject.toml
Dùng choQuick scriptsCI/CDML/DSLibraries/AppsMọi project

Chiến lược cách ly

Per-project venv (khuyến nghị mặc định): Mỗi project có .venv/ riêng ngay trong thư mục project. Dễ hiểu, dễ tái tạo, dễ xóa.

my-project/
├── .venv/              ← Environment riêng
├── src/
│   └── my_package/
├── tests/
├── pyproject.toml
└── .gitignore          ← Chứa .venv/

Centralized environments: Tất cả venv để chung tại ~/.virtualenvs/. Phù hợp khi làm việc với nhiều project nhỏ, nhưng khó track environment nào thuộc project nào.

Docker-based isolation: Cách ly hoàn toàn ở mức OS. Dùng khi cần đảm bảo production parity hoặc dependency có native components phức tạp.


Thực chiến

Tình huống: Thiết lập môi trường tái tạo được cho team 5 người

Bối cảnh: Team phát triển REST API bằng FastAPI, cần đảm bảo mọi thành viên và CI/CD pipeline dùng chính xác cùng dependencies. Đã xảy ra 2 lần "works on my machine" trong sprint vừa rồi.

Mục tiêu: Mọi environment — dev local, CI, staging, production — phải identical về dependency tree.

Bước 1: Khởi tạo project với uv (hoặc Poetry)

bash
# Lead khởi tạo project
uv init api-service
cd api-service

# Thêm production dependencies
uv add fastapi uvicorn[standard] sqlalchemy httpx pydantic-settings

# Thêm dev dependencies
uv add pytest pytest-asyncio ruff mypy --dev

# Lock dependencies — tạo uv.lock
uv lock

Bước 2: Cấu hình .gitignore và commit lock file

bash
# .gitignore — PHẢI có
.venv/
__pycache__/
*.pyc
.mypy_cache/
.pytest_cache/
.ruff_cache/
dist/
bash
# Commit CẢ hai file — đây là điều kiện bắt buộc
git add pyproject.toml uv.lock .gitignore
git commit -m "chore: init project with pinned dependencies"

Bước 3: Thành viên mới onboard

bash
# Clone repo
git clone git@company.com:team/api-service.git
cd api-service

# Một lệnh duy nhất — tạo venv + cài chính xác dependency tree từ lock file
uv sync

# Chạy tests để verify
uv run pytest

# Chạy dev server
uv run uvicorn src.main:app --reload

Bước 4: CI/CD pipeline (GitHub Actions)

yaml
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: astral-sh/setup-uv@v3
      - run: uv sync --frozen   # --frozen = fail nếu lock file outdated
      - run: uv run pytest --tb=short
      - run: uv run ruff check src/
      - run: uv run mypy src/

Phân tích:

  • uv sync --frozen đảm bảo CI dùng chính xác lock file đã commit, không resolve lại. Nếu ai đó thêm dependency mà quên update lock file, CI fail ngay lập tức.
  • Dev dependencies được tách riêng group dev — production deploy chỉ cần uv sync --no-dev.
  • Mọi thành viên chạy uv sync thay vì pip install — đảm bảo deterministic.

Bước 5: Cập nhật dependencies an toàn

bash
# Khi cần upgrade một package cụ thể
uv lock --upgrade-package httpx

# Khi cần upgrade tất cả
uv lock --upgrade

# Sau khi upgrade, PHẢI chạy tests
uv sync
uv run pytest

# Commit lock file sau khi verify
git add uv.lock
git commit -m "chore: upgrade httpx to 0.28.0"

Sai lầm điển hình

Sai lầm 1: Không dùng virtual environment

Vấn đề: Cài tất cả packages vào system Python.

bash
# SAI: Cài trực tiếp vào system
pip install requests flask numpy pandas
# Mọi project dùng chung → conflict khi version khác nhau
# Upgrade cho project A → break project B

Tại sao sai: System Python là shared resource. Trên Linux, nhiều system tool phụ thuộc vào system Python packages. Cài thêm hoặc upgrade package có thể break apt, yum, hoặc tool hệ thống khác. Trên macOS, system Python thuộc quản lý của OS — Apple khuyến cáo không modify.

bash
# ĐÚNG: Luôn tạo venv trước khi làm bất cứ gì
python -m venv .venv
source .venv/bin/activate
pip install requests flask

Sai lầm 2: Dùng pip freeze thay vì lock file đúng cách

Vấn đề: pip freeze dump toàn bộ environment, bao gồm cả transitive dependencies và dev tools.

bash
# SAI: Freeze tất cả — không phân biệt direct vs transitive
pip freeze > requirements.txt
# Output bao gồm: black, pytest, mypy (dev tools không cần trên production)
# Và: certifi, charset-normalizer, idna, urllib3 (transitive deps của requests)
# Không biết package nào bạn cài, package nào là dependency của dependency

Tại sao sai: Khi cần upgrade requests, bạn không biết certifi hay urllib3 là dependency của nó hay của package khác. Xóa requests khỏi requirements.txt nhưng certifi vẫn còn — rác tích tụ. Sau vài tháng, requirements.txt trở thành danh sách 200 packages mà không ai hiểu cái nào cần cái nào.

bash
# ĐÚNG: Dùng tool phân biệt direct vs transitive dependencies
# Option 1: uv
uv add requests    # Ghi vào pyproject.toml, lock vào uv.lock

# Option 2: pip-tools
echo "requests" > requirements.in
pip-compile requirements.in   # Output requirements.txt với annotation rõ ràng

Sai lầm 3: Không pin Python version

Vấn đề: Dependency hoạt động trên Python 3.11 nhưng fail trên Python 3.12 do breaking change.

toml
# SAI: Không khai báo Python version
[project]
name = "my-service"
dependencies = ["requests>=2.28"]
# Team member A dùng Python 3.11, member B dùng 3.13
# CI chạy 3.12. Production chạy 3.11. Chaos.

Tại sao sai: Python minor version thay đổi có thể remove deprecated features, thay đổi behavior của stdlib, hoặc break C extension compatibility. asyncio thay đổi đáng kể giữa 3.10 và 3.12. datetime thay đổi timezone handling giữa 3.11 và 3.12.

toml
# ĐÚNG: Pin Python version range chặt chẽ
[project]
name = "my-service"
requires-python = ">=3.12,<3.14"
dependencies = ["requests>=2.28,<3.0"]

Sai lầm 4: Trộn lẫn package managers trong cùng project

Vấn đề: Dùng conda cài một số packages, pip cài số khác, metadata bị desync.

bash
# SAI: Trộn conda và pip không kiểm soát
conda install numpy scipy
pip install pandas    # pandas thấy numpy từ conda nhưng metadata khác
pip install torch     # torch kéo numpy version khác → conflict
conda update numpy    # pip packages không biết numpy đã thay đổi

Tại sao sai: conda và pip dùng metadata system khác nhau. conda không biết pip đã cài gì và ngược lại. Khi upgrade, mỗi tool chỉ thấy packages của mình → dependency resolution sai → runtime error khó debug.

bash
# ĐÚNG: Chọn MỘT tool và kiên định
# Nếu dùng conda: cài TẤT CẢ bằng conda trước
conda install numpy scipy pandas torch
# Chỉ dùng pip cho package KHÔNG có trên conda, và cài SAU CÙNG
pip install some-niche-package

Sai lầm 5: Không commit lock file

Vấn đề: Chỉ commit pyproject.toml, không commit uv.lock hoặc poetry.lock.

bash
# SAI: Lock file trong .gitignore (hoặc quên add)
echo "uv.lock" >> .gitignore   # Sai lầm nghiêm trọng!
git add pyproject.toml
git commit -m "add deps"
# Mỗi lần cài sẽ resolve lại → version khác nhau trên mỗi máy

Tại sao sai: pyproject.toml chứa version range (>=2.28,<3.0). Lock file chứa version chính xác (2.31.0) cùng hash và toàn bộ transitive tree. Không có lock file = mỗi uv sync có thể cho kết quả khác nhau tùy thời điểm resolve. Đây chính là nguyên nhân của incident mở đầu bài viết.

bash
# ĐÚNG: Lock file LUÔN được commit
git add pyproject.toml uv.lock
git commit -m "chore: pin dependencies with lock file"
# CI dùng --frozen để fail nếu lock file outdated

Under the Hood

Cơ chế hoạt động của venv

Khi chạy python -m venv .venv, Python thực hiện:

  1. Tạo cấu trúc thư mục: .venv/bin/ (hoặc Scripts/ trên Windows), .venv/lib/pythonX.Y/site-packages/, .venv/include/
  2. Symlink Python binary: .venv/bin/python → symlink đến system Python (không copy toàn bộ interpreter)
  3. Tạo file pyvenv.cfg: Chứa metadata — đường dẫn system Python gốc, version, include-system-site-packages flag
  4. Cài pipsetuptools: Seed packages để có thể cài thêm packages
python
# Đọc pyvenv.cfg để hiểu cấu hình environment
from pathlib import Path

cfg_path = Path(".venv/pyvenv.cfg")
config = {}
for line in cfg_path.read_text().splitlines():
    if "=" in line:
        key, value = line.split("=", 1)
        config[key.strip()] = value.strip()

print(config)
# {'home': '/usr/bin', 'include-system-site-packages': 'false',
#  'version': '3.12.4', 'executable': '/usr/bin/python3.12', ...}

Activate script hoạt động ra sao: Script activate chỉ làm hai việc — prepend .venv/bin vào $PATH và set biến $VIRTUAL_ENV. Khi gõ python, shell tìm trong .venv/bin/ trước, gặp symlink trỏ đến system Python nhưng với sys.prefix trỏ về .venv/. Python interpreter dựa vào sys.prefix để tìm site-packages → packages trong venv được load thay vì system packages.

python
import sys
import site

# Trong venv, sys.prefix khác sys.base_prefix
print(f"prefix:      {sys.prefix}")       # .venv path
print(f"base_prefix: {sys.base_prefix}")   # system Python path

# site-packages path bên trong venv
print(site.getsitepackages())
# ['/path/to/.venv/lib/python3.12/site-packages']

So sánh hiệu năng giữa các tool

Benchmark ước lượng trên project trung bình (~30 direct dependencies):

Thao tácpipPoetryuv
Tạo venv~3s~5s~0.2s
Install từ lock (cold)~45s~60s~3s
Install từ lock (warm cache)~15s~20s~1s
Resolve dependencies~10s~15s~0.5s
Lock file updateN/A~20s~1s

uv nhanh hơn đáng kể nhờ viết bằng Rust, dùng parallel downloads, và aggressive caching. Trên CI/CD pipeline chạy hàng trăm lần mỗi ngày, sự khác biệt này tiết kiệm hàng giờ compute time mỗi tháng.

Khi nào dùng tool nào

Tình huốngKhuyến nghịLý do
Quick script, automationvenv + pipĐơn giản, không cần cài thêm gì
Web app, API serviceuv hoặc PoetryLock file, dependency groups, build support
ML / Data SciencecondaNative libs (CUDA, cuDNN, BLAS)
Library để publish lên PyPIPoetry hoặc uvBuild system tích hợp
CI/CD pipeline cần tốc độuv10-100x nhanh hơn pip
Team đang dùng pip-toolspip-toolsKhông cần migration nếu đang ổn
Project mới 2024+uvNhanh, hiện đại, đang trở thành chuẩn mới

Trade-offs

venv: Không cần cài gì, có sẵn mọi nơi. Nhưng không có lock file, không quản lý dependency groups, không hỗ trợ build/publish.

Poetry: Feature đầy đủ nhất, dependency resolution mạnh. Nhưng chậm, đôi khi conflict với pip ecosystem, và resolver có thể mất nhiều phút cho dependency tree lớn.

uv: Nhanh vượt trội, API đơn giản, đang được adopt rộng rãi. Nhưng còn mới (khởi đầu 2024), một số edge case chưa cover, ecosystem plugin chưa phong phú bằng Poetry.

conda: Quản lý được cả non-Python packages — không thể thay thế cho ML/DS workload cần CUDA. Nhưng nặng, chậm, và dễ conflict khi trộn với pip.


Checklist ghi nhớ

✅ Checklist triển khai

Thiết lập project

  • [ ] Tạo virtual environment trước khi cài bất kỳ package nào
  • [ ] Thêm .venv/ vào .gitignore
  • [ ] Khai báo requires-python trong pyproject.toml
  • [ ] Chọn một package manager cho project và kiên định dùng nó

Dependency management

  • [ ] Phân biệt direct dependencies vs transitive dependencies
  • [ ] Tách dev dependencies ra group riêng (--dev hoặc --group dev)
  • [ ] Commit lock file (uv.lock, poetry.lock) vào version control
  • [ ] CI/CD dùng --frozen flag để fail khi lock file outdated

Bảo trì

  • [ ] Upgrade dependencies định kỳ (ít nhất mỗi sprint) và chạy tests sau khi upgrade
  • [ ] Không trộn package managers trong cùng project (conda + pip = rủi ro)
  • [ ] Xóa và tạo lại venv khi nghi ngờ environment bị corrupt
  • [ ] Document tool và version trong README để thành viên mới onboard nhanh

Bài tập luyện tập

Bài 1 — Foundation: Kiểm tra và introspect virtual environment

Yêu cầu: Viết hàm inspect_environment() trả về dictionary chứa thông tin đầy đủ về Python environment hiện tại — có đang trong venv không, đường dẫn venv, Python version, danh sách installed packages.

python
# Expected output (khi chạy trong venv):
# {
#     'in_venv': True,
#     'venv_path': '/path/to/.venv',
#     'python_version': '3.12.4',
#     'python_executable': '/path/to/.venv/bin/python',
#     'installed_packages': {'requests': '2.31.0', 'flask': '3.0.0', ...},
#     'site_packages_path': '/path/to/.venv/lib/python3.12/site-packages'
# }

🧠 Quiz

Câu hỏi: Làm thế nào Python biết nó đang chạy bên trong virtual environment?

  • [ ] A. Kiểm tra biến môi trường $VIRTUAL_ENV
  • [ ] B. Đọc file .venv/pyvenv.cfg
  • [x] C. So sánh sys.prefix với sys.base_prefix — nếu khác nhau thì đang trong venv
  • [ ] D. Kiểm tra xem pip có trong PATH hay không Giải thích: sys.prefix trỏ đến thư mục venv khi activate, trong khi sys.base_prefix luôn trỏ đến system Python gốc. Option A chỉ hoạt động nếu shell đã source activate script — Python process có thể chạy trong venv mà không qua activate (ví dụ: .venv/bin/python script.py).
💡 Gợi ý
  • Dùng sys.prefixsys.base_prefix để kiểm tra venv
  • importlib.metadata.distributions() liệt kê packages đã cài
  • site.getsitepackages() trả về đường dẫn site-packages
✅ Lời giải
python
import sys
import site
from importlib.metadata import distributions
from pathlib import Path


def inspect_environment() -> dict:
    """Introspect Python environment hiện tại."""
    in_venv = sys.prefix != sys.base_prefix

    installed = {}
    for dist in distributions():
        installed[dist.metadata["Name"]] = dist.metadata["Version"]

    site_packages = site.getsitepackages()

    return {
        "in_venv": in_venv,
        "venv_path": sys.prefix if in_venv else None,
        "python_version": f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}",
        "python_executable": sys.executable,
        "installed_packages": dict(sorted(installed.items())),
        "site_packages_path": site_packages[0] if site_packages else None,
    }


if __name__ == "__main__":
    import json
    info = inspect_environment()
    print(json.dumps(info, indent=2, default=str))

Phân tích: Hàm không phụ thuộc vào biến môi trường $VIRTUAL_ENV (có thể không tồn tại nếu chạy trực tiếp bằng .venv/bin/python). importlib.metadata là API chuẩn từ Python 3.8+ để đọc package metadata từ dist-info, thay thế cho pkg_resources đã deprecated.

Bài 2 — Intermediate: Script tự động hóa setup project

Yêu cầu: Viết script Python bootstrap.py thực hiện:

  1. Kiểm tra Python version >= 3.11
  2. Tạo venv nếu chưa có
  3. Cài dependencies từ requirements.txt (nếu tồn tại) hoặc pyproject.toml
  4. Verify installation thành công
  5. In hướng dẫn activate cho user
python
# Chạy: python bootstrap.py
# Output expected:
# ✅ Python 3.12.4 — OK
# ✅ Created virtual environment at .venv
# ✅ Installed 15 packages from requirements.txt
# ✅ Verification passed — all imports OK
#
# Activate with:
#   source .venv/bin/activate     (Linux/macOS)
#   .venv\Scripts\activate        (Windows)

🧠 Quiz

Câu hỏi: Script bootstrap.py nên chạy bằng system Python hay venv Python?

  • [x] A. System Python — vì venv chưa tồn tại tại thời điểm chạy script
  • [ ] B. Venv Python — vì cần cài packages vào venv
  • [ ] C. Không quan trọng — Python nào cũng được
  • [ ] D. Chỉ chạy được bằng uv runGiải thích: bootstrap.py là script khởi tạo — nó tạo venv, rồi gọi pip bên trong venv đó thông qua subprocess. Bản thân script chạy bằng system Python nhưng mọi pip install đều target venv Python executable.
💡 Gợi ý
  • Dùng venv.create() từ stdlib thay vì subprocess python -m venv
  • Dùng subprocess.run([venv_pip, "install", ...]) để cài packages vào venv
  • Kiểm tra sys.version_info để verify Python version
✅ Lời giải
python
"""bootstrap.py — Tự động thiết lập development environment."""
import os
import subprocess
import sys
import venv
from pathlib import Path

MINIMUM_PYTHON = (3, 11)
VENV_DIR = Path(".venv")


def check_python_version() -> None:
    """Verify Python version đủ yêu cầu."""
    current = sys.version_info[:2]
    if current < MINIMUM_PYTHON:
        min_str = ".".join(map(str, MINIMUM_PYTHON))
        cur_str = ".".join(map(str, current))
        print(f"❌ Python {min_str}+ required, got {cur_str}")
        sys.exit(1)
    version = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
    print(f"✅ Python {version} — OK")


def create_venv() -> Path:
    """Tạo virtual environment nếu chưa tồn tại."""
    if VENV_DIR.exists():
        print(f"✅ Virtual environment already exists at {VENV_DIR}")
    else:
        venv.create(VENV_DIR, with_pip=True)
        print(f"✅ Created virtual environment at {VENV_DIR}")

    if os.name == "nt":
        return VENV_DIR / "Scripts" / "pip.exe"
    return VENV_DIR / "bin" / "pip"


def install_dependencies(pip_path: Path) -> int:
    """Cài dependencies từ requirements.txt hoặc pyproject.toml."""
    req_file = Path("requirements.txt")
    pyproject = Path("pyproject.toml")

    if req_file.exists():
        result = subprocess.run(
            [str(pip_path), "install", "-r", str(req_file), "--quiet"],
            capture_output=True,
            text=True,
        )
        source = req_file.name
    elif pyproject.exists():
        result = subprocess.run(
            [str(pip_path), "install", ".", "--quiet"],
            capture_output=True,
            text=True,
        )
        source = pyproject.name
    else:
        print("⚠️  No requirements.txt or pyproject.toml found")
        return 0

    if result.returncode != 0:
        print(f"❌ Installation failed:\n{result.stderr}")
        sys.exit(1)

    # Đếm packages đã cài
    list_result = subprocess.run(
        [str(pip_path), "list", "--format=columns"],
        capture_output=True,
        text=True,
    )
    count = len(list_result.stdout.strip().splitlines()) - 2
    print(f"✅ Installed {count} packages from {source}")
    return count


def print_activation_instructions() -> None:
    """In hướng dẫn activate cho user."""
    print()
    print("Activate with:")
    print(f"  source {VENV_DIR}/bin/activate     (Linux/macOS)")
    print(f"  {VENV_DIR}\\Scripts\\activate        (Windows)")


def main() -> None:
    check_python_version()
    pip_path = create_venv()
    install_dependencies(pip_path)
    print_activation_instructions()


if __name__ == "__main__":
    main()

Phân tích: Script dùng venv.create() (stdlib) thay vì subprocess — tránh dependency vào shell command. Mỗi hàm có trách nhiệm đơn lẻ. Error handling thoát sớm với sys.exit(1) khi phát hiện lỗi. Script tương thích cả Linux/macOS và Windows.


Liên kết học tiếp

Glossary keywords: virtual environment, venv, virtualenv, conda, Poetry, uv, pip, pip-tools, site-packages, sys.prefix, sys.base_prefix, isolation, lock file, dependency resolution, reproducible builds

Tìm kiếm liên quan: python virtual environment, tạo môi trường ảo python, cách dùng venv, so sánh conda poetry uv, dependency management python