Virtual Environments & pip
ทุกโปรเจกต์ Python ควรรันอยู่ภายใน environment ที่แยกออกมาของตัวเอง หากไม่มี ทุกการติดตั้งจะลงไปยัง Python installation ที่ใช้ร่วมกันตัวเดียว ซึ่งแพ็กเกจจากโปรเจกต์ต่าง ๆ จะชนกันและความขัดแย้งของเวอร์ชันจะสะสมอย่างเงียบ ๆ บทนี้จะสอนเครื่องมือสมัยใหม่ก่อน — uv — แล้วค่อยแสดง venv/pip ที่อยู่ข้างใต้ เพราะ uv จัดการทั้งสองอย่างให้คุณ แต่ทุกโปรเจกต์ที่คุณเจอที่เก่ากว่าปี 2024 ยังใช้เครื่องมือดิบเหล่านี้ตรง ๆ อยู่
บทนี้ไม่มีปุ่ม Run —
uv/venv/pip ต้องรันในเครื่อง:uv init myapp && cd myapp && uv add requests
uv — เครื่องมือเดียวสำหรับ interpreter, environment และ dependency
หัวข้อที่มีชื่อว่า “uv — เครื่องมือเดียวสำหรับ interpreter, environment และ dependency”uv เป็น binary ตัวเดียวที่แทนที่ venv + pip + pip-tools + pyenv + pipx มัน resolve และติดตั้งเร็วกว่า pip ราวสิบเท่า เพราะเขียนด้วย Rust และ cache อย่างจริงจังข้ามโปรเจกต์
# create a new project: pyproject.toml, .python-version, src/ layout, git inituv init myapp && cd myapp# pyproject.toml (generated by uv init)[project]name = "myapp"version = "0.1.0"description = "Add your description here"readme = "README.md"requires-python = ">=3.14"dependencies = []uv init ยังเขียนไฟล์ .python-version ด้วย — output จริงบนเครื่องนี้:
$ cat .python-version3.14ทุก uv run/uv sync ในไดเรกทอรีนี้ (และ subdirectory) จะเล็ง interpreter เวอร์ชันนั้นทันที โดยที่คุณไม่ต้องตั้ง environment variable หรือ activate อะไรเลย
การเพิ่ม ลบ และรัน
หัวข้อที่มีชื่อว่า “การเพิ่ม ลบ และรัน”uv add requests # resolve, download, install, record in pyproject.toml + uv.lockuv remove requests # uninstall + remove from pyproject.toml + relockuv run python main.py # run inside the project's environment -- no activation neededรันจริง เพิ่ม requests ในโปรเจกต์ใหม่:
$ uv add requestsUsing CPython 3.14.6Creating virtual environment at: .venvResolved 6 packages in 367msInstalled 5 packages in 10ms + certifi==2026.7.22 + charset-normalizer==3.5.1 + idna==3.20 + requests==2.34.2 + urllib3==2.8.0uv add สร้าง .venv ให้เองโดยที่คุณไม่เคยรัน python -m venv หรือ activate อะไรเลย uv run ใช้ environment นั้นโดยอัตโนมัติ:
$ echo 'import requestsprint(requests.__version__)' > main.py$ uv run python main.py2.34.2การลบก็ทำงานแบบเดียวกันในทิศตรงกันข้าม — uv remove requests ถอนการติดตั้งแพ็กเกจ และ dependency ทุกตัวที่ requests ต้องการเพียงผู้เดียว แล้วเขียน pyproject.toml ใหม่:
$ uv remove requestsResolved 1 package in 3msUninstalled 5 packages in 12ms - certifi==2026.7.22 - charset-normalizer==3.5.1 - idna==3.20 - requests==2.34.2 - urllib3==2.8.0uv.lock — lockfile ที่ครอบคลุมทุกแพลตฟอร์ม
หัวข้อที่มีชื่อว่า “uv.lock — lockfile ที่ครอบคลุมทุกแพลตฟอร์ม”uv add ยังเขียน uv.lock ด้วย ซึ่งบันทึกทุกแพ็กเกจที่ resolve แล้ว เวอร์ชันของมัน และ hash ของไฟล์ — สำหรับทุกแพลตฟอร์ม ไม่ใช่แค่ตัวที่คุณใช้อยู่ นี่คือสิ่งเดียวที่ requirements.txt ธรรมดาไม่เคยทำได้ดี
$ head -12 uv.lockversion = 1revision = 3requires-python = ">=3.14"
[[package]]name = "certifi"version = "2026.7.22"source = { registry = "https://pypi.org/simple" }sdist = { url = "https://files.pythonhosted.org/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", size = 138112, upload-time = "2026-07-22T03:35:12.644Z" }wheels = [ { url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983, upload-time = "2026-07-22T03:35:11.276Z" },]uv sync สร้าง environment ขึ้นใหม่จาก uv.lock ให้ตรงเป๊ะ บนเครื่องไหนหรือ CI runner ไหนก็ได้:
$ uv syncResolved 6 packages in 3msChecked 5 packages in 0.11msuv lock --check ตรวจว่า pyproject.toml กับ uv.lock ยังตรงกันอยู่ โดยไม่ติดตั้งอะไร — คือ check ที่ควรรันใน CI ก่อน uv sync --frozen:
$ uv lock --checkResolved 6 packages in 3mscommit ทั้ง pyproject.toml และ uv.lock เข้า version control อย่า commit .venv/ เด็ดขาด
uv tool / uvx — รัน CLI tool โดยไม่ปนกับโปรเจกต์
หัวข้อที่มีชื่อว่า “uv tool / uvx — รัน CLI tool โดยไม่ปนกับโปรเจกต์”บางแพ็กเกจเป็นเครื่องมือที่คุณ รัน (ruff, mypy, httpie) ไม่ใช่ library ที่โค้ดคุณ import uv tool install จะติดตั้งมันลง environment แยกของตัวเองแล้วเชื่อม executable เข้า PATH ของคุณ; uvx รันเครื่องมือครั้งเดียวแบบชั่วคราว ไม่ติดตั้งถาวรเลย — คำสั่งของคอร์สนี้เอง (uvx ruff, uvx mypy) ก็ใช้แบบนี้เป๊ะ ๆ
$ uv tool install ruffResolved 1 package in 268msInstalled 1 package in 4ms + ruff==0.16.8Installed 1 executable: ruff
$ uvx ruff --versionruff 0.16.8uv python — จัดการตัว interpreter เอง
หัวข้อที่มีชื่อว่า “uv python — จัดการตัว interpreter เอง”$ uv python list --only-installedcpython-3.14.7-macos-aarch64-none /opt/homebrew/bin/python3.14 -> ../Cellar/[email protected]/3.14.7/bin/python3.14cpython-3.14.6-macos-aarch64-none /Users/.../uv/python/cpython-3.14-macos-aarch64-none/bin/python3.14cpython-3.13.14-macos-aarch64-none /opt/homebrew/bin/python3.13 -> ../Cellar/[email protected]/3.13.14/bin/python3.13uv สามารถดาวน์โหลดและจัดการสำเนา CPython ของตัวเองได้ (cpython-3.14.6 ข้างบนมาจาก uv python install ที่ถูกเรียกอัตโนมัติโดย uv add เมื่อไม่มี interpreter ไหนตรงกับ requires-python) — แยกขาดจาก system Python หรือ Homebrew install ใด ๆ โดยสิ้นเชิง uv python pin 3.14 จะเขียน .python-version; เพื่อนร่วมทีมและ CI runner ทุกตัวที่ใช้ uv เหมือนกันจะได้ interpreter ตัวเดียวกันเป๊ะ ไม่ใช่ “อะไรก็ตามที่ python3 บังเอิญชี้ไปถึง”
uv pip — ทางออกสำหรับ workflow แบบ pip
หัวข้อที่มีชื่อว่า “uv pip — ทางออกสำหรับ workflow แบบ pip”สำหรับสคริปต์หรือขั้นตอน CI ที่ยังคาดหวัง interface แบบ pip เป๊ะ ๆ uv pip คือ reimplementation ที่เร็วกว่ามากและใช้แทนกันได้ทันที ทำงานบน virtual environment เดียวกัน:
$ uv pip listPackage Version------------------ ---------certifi 2026.7.22charset-normalizer 3.5.1idna 3.20requests 2.34.2urllib3 2.8.0
$ uv pip install --dry-run tomliResolved 1 package in 630msWould download 1 packageWould install 1 package + tomli==2.4.1uv pip ไม่อัปเดต pyproject.toml หรือ uv.lock — มันมีไว้เพื่อความเข้ากันได้กับ tooling แบบ pip เดิม ไม่ใช่สำหรับจัดการโปรเจกต์แบบ uv-native ให้เลือกใช้ uv add/uv remove สำหรับสิ่งที่คุณตั้งใจจะเก็บไว้จริง ๆ
สิ่งที่อยู่ข้างใต้: venv และ pip
หัวข้อที่มีชื่อว่า “สิ่งที่อยู่ข้างใต้: venv และ pip”uv จัดการ virtual environment แบบ venv และเรียก resolver ที่เข้ากันได้กับ pip ให้คุณ การเข้าใจเครื่องมือดิบสำคัญ เพราะโค้ด Python ส่วนใหญ่ที่คุณจะเจอ — tutorial, repo เก่า, CI config — ยังใช้มันตรง ๆ อยู่
# create a virtual environment in .venv/, with the stdlib venv modulepython -m venv .venvการ activate ไม่ใช่เวทมนตร์ — มันแค่เพิ่มไดเรกทอรี bin/ ของ environment ไว้หน้า PATH ของ shell คุณ เพื่อให้ python และ pip แบบไม่ระบุ path ชี้ไปยังสำเนาที่แยกออกมา แทนที่จะเป็น system interpreter:
$ echo "$PATH" | cut -c1-90 # before activation/opt/homebrew/opt/postgresql-client/bin:/Users/avetavos/google-cloud-sdk/bin:/Users/av
$ source .venv/bin/activate$ echo "$PATH" | cut -c1-90 # after activation -- .venv/bin prepended/private/tmp/.../plainvenv/.venv/bin:/opt/homebrew/opt/postgresql-client/bin:/Users/avไม่มีอะไรอื่นเปลี่ยนเลย: ไม่มี process ใหม่ ไม่มี interpreter ถูกสลับให้ใต้ดินโดยไม่รู้ตัว deactivate (function ของ shell ที่ activate นิยามไว้) แค่คืนค่า PATH เดิม
python -m pip เทียบกับ pip เปล่า
หัวข้อที่มีชื่อว่า “python -m pip เทียบกับ pip เปล่า”$ which python.../plainvenv/.venv/bin/python$ which pip.../plainvenv/.venv/bin/pip$ python -m pip --versionpip 26.2.1 from .../plainvenv/.venv/lib/python3.14/site-packages/pip (python 3.14)$ pip --versionpip 26.2.1 from .../plainvenv/.venv/lib/python3.14/site-packages/pip (python 3.14)ภายใน venv ที่ active อยู่และสร้างมาถูกต้อง ทั้งสองแบบเหมือนกัน เพราะ pip บน PATH คือ pip ของ interpreter ตัวนั้นเอง มันจะต่างกันทันทีที่ PATH โกหกคุณ — การ activate ที่พัง, pip ที่ติดตั้งด้วย sudo บัง pip ของ venv เอง หรือสคริปต์ที่ลืม activate ก่อน python -m pip ติดตั้งเข้า interpreter ที่รันมันเสมอ เพราะ Python จะ resolve module เทียบกับ sys.executable ไม่ใช่เทียบกับ pip ตัวไหนก็ตามที่ PATH เก่าบังเอิญเจอ ห้ามรัน sudo pip install เด็ดขาด — มันติดตั้งเข้า system Python ที่ tooling ของ macOS/Linux เองยังพึ่งพาอยู่ ด้วยสิทธิ์ root ข้าม virtual environment ไปทั้งหมด; system Python ที่พังจาก sudo pip ที่ผิดพลาดอาจทำ OS tooling ที่ไม่เกี่ยวข้องพังไปด้วย
pip install -e . — editable install
หัวข้อที่มีชื่อว่า “pip install -e . — editable install”$ python -m pip install -e . --quiet$ python -c "import edemo; print(edemo.greet())"v1$ sed -i '' 's/v1/v2 (edited, no reinstall)/' src/edemo/__init__.py$ python -c "import edemo; print(edemo.greet())"v2 (edited, no reinstall)-e (editable/development mode) ติดตั้ง link ไปยัง source tree ของคุณแทนที่จะ copy ไฟล์ — การแก้ไขมีผลตั้งแต่ import ครั้งถัดไป โดยไม่ต้อง reinstall นี่คือวิธีที่คุณพัฒนา package ที่กำลังถูกใช้จากสคริปต์หรือ test suite อีกตัวไปพร้อมกัน
pip freeze เทียบกับ lockfile จริง
หัวข้อที่มีชื่อว่า “pip freeze เทียบกับ lockfile จริง”$ python -m pip install requests --quiet$ pip freezecertifi==2026.7.22charset-normalizer==3.5.1idna==3.20requests==2.34.2urllib3==2.8.0pip freeze > requirements.txt จับภาพเวอร์ชันที่แน่นอนของ สิ่งที่ติดตั้งอยู่ตอนนี้พอดี — ไม่มี file hash ไม่มี per-platform wheel resolution ไม่มีการแยกระหว่าง “ฉันขอสิ่งนี้เอง” กับ “สิ่งนี้ถูกดึงมาเป็น dependency ของอย่างอื่น” uv.lock บันทึก hash และ wheel เฉพาะแพลตฟอร์มของทุกแพ็กเกจโดยดีฟอลต์; requirements.txt ธรรมดาจะมี hash ก็ต่อเมื่อคุณรัน pip-compile --generate-hashes หรือ uv export --no-hashes=false เองเท่านั้น — requirements.txt ส่วนใหญ่ในโลกจริงไม่มี hash เลย ซึ่งหมายความว่า pip install -r requirements.txt จะยินดีติดตั้งแพ็กเกจที่ถูกปลอมแปลงที่มี เลขเวอร์ชัน ถูกต้องแต่เป็น ไฟล์ คนละไฟล์
flowchart TD A["uv init myapp"] --> B["uv add requests"] B --> C[".venv created + pyproject.toml + uv.lock written"] C --> D["uv run ... / uv sync"] D -->|"reproduces exactly"| E["teammate or CI: uv sync --frozen"] F["python -m venv .venv"] --> G["source .venv/bin/activate (PATH only)"] G --> H["python -m pip install ..."] H --> I["pip freeze > requirements.txt (no hashes, no per-platform lock)"]
Mental Model
หัวข้อที่มีชื่อว่า “Mental Model”uv ไม่ใช่ package format ใหม่ — มันคือ implementation ที่เร็วกว่าของงานสามอย่างเดิมที่ pyenv + venv + pip แยกกันทำ: ดึง interpreter มา, สร้าง environment ที่แยกออกมา, resolve และติดตั้งแพ็กเกจเข้าไป uv.lock คือสิ่งที่ output ของ pip freeze พยายามจะเป็นมาตลอด: คำอธิบายของ dependency graph ทั้งหมด ที่ pin ด้วย hash สำหรับ ทุก target platform ไม่ใช่แค่ภาพนิ่งของ site-packages บนเครื่องเดียวตอนนี้
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| สิ่งที่ได้ | ประโยชน์ | ต้นทุน |
|---|---|---|
uv add/uv run | ไม่ต้องสร้างหรือ activate venv เอง เป็น project-scoped โดยโครงสร้าง | ต้องติดตั้งเครื่องมือเพิ่ม (แม้จะเป็น static binary ตัวเดียว) |
uv.lock | pin ด้วย hash, ครอบคลุมทุกแพลตฟอร์ม, reproduce ได้ทุกที่ | แก้ด้วยมือแบบ requirements.txt ไม่ได้; ต้องใช้ uv (หรือ uv export) เพื่ออ่าน |
uv pip compatibility layer | ใช้กับ CI/สคริปต์แบบ pip ได้ทันทีด้วย resolver ที่เร็วกว่า | ข้าม bookkeeping ของ pyproject.toml/uv.lock — ไม่เหมาะสำหรับจัดการโปรเจกต์แบบ uv-native |
venv + pip ตรง ๆ | ใช้แค่ stdlib + แพ็กเกจ PyPI ตัวเดียว ไม่ต้องติดตั้งอะไรเพิ่ม | activate เอง, ไม่มี lockfile ในตัว, ติดตั้งเข้า interpreter ผิดตัวได้ง่าย |
pip freeze > requirements.txt | ง่าย อ่านได้ด้วยตาคน | ไม่มี hash โดยดีฟอลต์, ไม่มี per-platform resolution, ปนกันระหว่าง direct กับ transitive dependency |
ความเข้าใจผิดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ความเข้าใจผิดที่พบบ่อย”- “การ activate venv จะเริ่ม Python process ใหม่” มันแค่แก้
PATH(และตัวแปร shell อีกไม่กี่ตัว) ใน shell ปัจจุบัน ของคุณ — ไม่มี process ไหนเริ่มจนกว่าคุณจะรันpythonครั้งถัดไป - “
pipกับpython -m pipใช้แทนกันได้เสมอ” ทั้งสองจะตรงกันก็ต่อเมื่อPATHชี้pipไปยังสำเนาของ interpreter ที่ active อยู่ถูกต้อง;python -m pipคือเวอร์ชันที่PATHเก่าหรือถูกบังไม่สามารถหลอกได้ - “
uv.lockเป็นตัวเลือก เหมือนที่requirements.txtเคยเป็นมาตลอด”uv sync/uv runอ่านมันเพื่อ reproduce environment ของคุณเป๊ะ; การลบมันทิ้ง (หรือไม่ commit) จะทำให้ปัญหา “works on my machine” ที่uv.lockมีไว้เพื่อกำจัดกลับมาอีก - “
requirements.txtpin แพ็กเกจที่แน่นอนและตรวจสอบได้เสมอ” มัน pin แค่ เลขเวอร์ชัน ตามธรรมเนียมเท่านั้น — ถ้าไม่มี--generate-hashesก็ไม่มีอะไรหยุดpipจากการติดตั้งไฟล์คนละไฟล์ภายใต้ version string เดียวกันจาก index ที่ถูกแฮ็กหรืออัปโหลดซ้ำ - “
sudo pip installเป็นทางแก้ error สิทธิ์ที่สมเหตุสมผล” error สิทธิ์ตอนติดตั้งแพ็กเกจแทบทุกครั้งหมายความว่าคุณไม่ได้อยู่ใน virtual environment ทางแก้คือสร้างและ activate สักตัว ไม่ใช่ติดตั้งด้วยสิทธิ์ root เข้า system Python
อ่านโค้ดที่ AI เขียน
หัวข้อที่มีชื่อว่า “อ่านโค้ดที่ AI เขียน”ก่อนเชื่อสคริปต์ setup หรือ requirements.txt ที่ generate มา ให้ตรวจ:
sudo pip install ...ที่ไหนก็ตามในสคริปต์หรือ README — แปลว่ามีคนข้าม virtual environment ไป ไม่ใช่ไม่จำเป็นน้อยลงrequirements.txtที่มี entry ไม่ pin เวอร์ชัน (requestsไม่มีเวอร์ชันเลย) อยู่ข้าง ๆ คำอ้างว่า environment “reproducible”subprocess.run(["pip", "install", ...])ที่ถูกเรียกจากในโค้ดแอปตอน runtime — dependency ควรอยู่ในpyproject.toml/requirements.txtติดตั้งก่อนโปรแกรมรัน ไม่ใช่เป็น side effect ของการรันโปรแกรม- สคริปต์ setup ที่ activate venv แล้วเรียก
pip(ไม่ใช่python -m pip) ในอีก subprocess ที่shell=False— activate มีผลแค่กับPATHของ shell ปัจจุบัน เท่านั้น ดังนั้นpipเปล่าใน subprocess ใหม่อาจไปชี้ที่ system pip แบบเงียบ ๆ
🐛 หา bug ในโค้ดนี้
import subprocessimport sys
def ensure_dependency(package: str) -> None: # BUG: installs into whatever interpreter/environment happens to be # active when this function runs, and does it every time the program # starts -- not once, at setup, into a lockfile-controlled environment. subprocess.run(["pip", "install", package], check=True)
def main() -> None: ensure_dependency("requests") import requests
print(requests.get("https://example.com", timeout=5).status_code)
if __name__ == "__main__": main()เฉลย
การติดตั้ง dependency จากใน main() ด้วย subprocess.run(["pip", "install", ...]) หมายความว่าทุกครั้งที่โปรแกรมรันสามารถติดตั้งหรืออัปเกรดแพ็กเกจแบบเงียบ ๆ ได้ ไม่มี lockfile ไม่มี pin ไม่มีบันทึกว่าอะไรเปลี่ยนไป — นี่คือ failure mode แบบ “works on my machine” เป๊ะ ๆ ที่ uv.lock/requirements.txt มีไว้ป้องกัน มันยังเรียก pip เปล่าแทนที่จะเป็น python -m pip (หรือ sys.executable) ดังนั้นใน subprocess ที่ PATH ต่างหรือหายไปมันอาจติดตั้งเข้า interpreter ผิดตัวไปเลย หรือ fail ไปเลย ทางแก้: ประกาศ requests เป็น dependency ใน pyproject.toml (uv add requests) ติดตั้งครั้งเดียวเป็นขั้นตอน setup (uv sync) แล้วให้โปรแกรมแค่ import requests และ fail แบบชัดเจนตอนเริ่มถ้ามันหายไป