- uv มีจุดเด่นด้านความเร็ว การจัดการเวอร์ชัน Python และการรวมหลายเครื่องมือไว้ในไบนารีเดียว แต่ UX ของการจัดการแพ็กเกจในช่วงบำรุงรักษายังขรุขระ
- สามารถตรวจสอบแพ็กเกจที่ล้าสมัยได้ด้วย
uv pip list --outdated แต่เพราะไม่ได้เป็นคำสั่งระดับบนสุดและอยู่ใต้เนมสเปซที่เข้ากันได้กับ pip จึงค้นพบได้ยาก
uv add pydantic จะเพิ่มข้อกำหนดแบบ ไม่มีขอบเขตบน โดยปริยาย เช่น pydantic>=2.13.4 ซึ่งเปิดให้ขยับข้ามเมเจอร์เวอร์ชันได้
- การอัปเกรดทั้งหมดทำผ่าน
uv lock --upgrade และหากต้องการอัปเกรดหลายแพ็กเกจเฉพาะเจาะจง ก็จำเป็นต้อง ใส่ --upgrade-package ซ้ำ ทำให้คำสั่งยืดยาว
- สามารถตั้งค่า
add-bounds = "major" เพื่อให้ได้ข้อกำหนดเริ่มต้นที่ปลอดภัยขึ้น แต่ยังเป็นฟีเจอร์พรีวิว และสำหรับแอปพลิเคชันก็ควรมี UX การอัปเดตที่ตรงไปตรงมามากกว่านี้
จุดแข็งของ uv และความไม่สะดวกในช่วงบำรุงรักษา
- uv ของ Astral มีจุดแข็งด้านความเร็ว, การจัดการเวอร์ชัน Python และการแทนที่หลายเครื่องมือด้วยไบนารีเดียว
- ขั้นตอนเริ่มต้นโปรเจกต์ Python ใหม่และเพิ่ม dependency ตัวแรกนั้นทำได้ง่าย แต่เมื่อโปรเจกต์เข้าสู่ช่วงบำรุงรักษา UX สำหรับการตรวจสอบแพ็กเกจที่ล้าสมัยและอัปเกรดเป็นประจำจะดูขรุขระกว่า pnpm หรือ Poetry
- ความไม่สะดวกหลักอยู่ที่การค้นหาคำสั่งสำหรับตรวจสอบแพ็กเกจที่ล้าสมัย การไม่มีขอบเขตบนของข้อกำหนดเวอร์ชันโดยปริยาย และความยืดยาวของคำสั่งอัปเกรด
การตรวจสอบแพ็กเกจที่ล้าสมัย
- ในโปรเจกต์ JavaScript สามารถใช้
pnpm outdated เพื่อตรวจดูแพ็กเกจที่ล้าสมัย เวอร์ชันปัจจุบัน เวอร์ชันล่าสุด และเวอร์ชันที่ข้อกำหนดอนุญาตได้อย่างกระชับ
- ใน uv ไม่มีคำสั่งระดับบนสุด
uv outdated และในตอนแรกจึงมีการใช้คำสั่งต่อไปนี้แทน
$ uv tree --outdated --depth 1
uv tree --outdated --depth 1 ไม่ได้กรองเฉพาะรายการที่ล้าสมัย แต่จะแสดงต้นไม้ dependency ระดับบนสุดทั้งหมดก่อน แล้วค่อยแนบหมายเหตุเล็ก ๆ ไว้ข้างรายการที่อัปเดตได้
- แม้จะมี dependency 50 ตัวและมีแพ็กเกจล้าสมัยเพียง 2 ตัว ก็ยังต้องไล่ดูรายการ 50 บรรทัด
poetry show --outdated ของ Poetry แม้ชื่อคำสั่งจะเข้าใจได้ยากกว่า แต่ผลลัพธ์จริงจะแสดงเฉพาะแพ็กเกจที่ล้าสมัย
ความเสี่ยงของข้อกำหนดเวอร์ชันโดยปริยาย
-
วิธีปริยายของ pnpm และ Poetry
pnpm add จะบันทึกข้อกำหนดแบบแคเร็ต เช่น ^1.23.4 ลงใน package.json
^1.23.4 อนุญาตเวอร์ชัน 1.x.x แต่จะไม่อัปเดตไปเป็น 2.0.0
- Poetry ก็ใช้รูปแบบอย่าง
>=1.23.4,<2.0.0 โดยปริยายเช่นกัน แม้จะอ่านยากกว่าแต่ให้ผลเหมือนกัน
- ในสองเครื่องมือนี้ ภายใต้สมมติฐานว่าแพ็กเกจทำตาม SemVer การรัน
pnpm update หรือ poetry update จะช่วยลดโอกาสที่บิลด์พังจากการเปลี่ยนแปลง API หลัก
-
วิธีปริยายของ uv
uv add pydantic จะเพิ่มข้อกำหนดแบบไม่มีขอบเขตบนใน pyproject.toml ดังนี้
dependencies = [
"pydantic>=2.13.4",
]
- ภายใต้ข้อกำหนดนี้ pydantic เวอร์ชัน 2, 3 หรือ 100 ก็ถือว่าอนุญาตทั้งหมด
- เมื่อรันการอัปเดตครั้งใหญ่ จึงไม่ได้รับแค่การแก้บั๊ก แต่ยังอาจรับเอาการเปลี่ยนแปลงที่ทำให้ความเข้ากันได้พังซึ่งผู้ดูแลทุกตัวในกราฟ dependency ปล่อยออกมาด้วย
- โดยเฉพาะในการบำรุงรักษาแอปพลิเคชัน สิ่งนี้อาจนำไปสู่ความเสี่ยงด้านเสถียรภาพ
UX ของคำสั่งอัปเกรด
- ใน pnpm และ Poetry การอัปเดตทั้งหมดทำได้ง่ายดังนี้
$ pnpm update
$ poetry update
- ใน uv จะใช้คำสั่งต่อไปนี้สำหรับการอัปเกรดทั้งหมด
$ uv lock --upgrade
uv lock --upgrade ไม่ได้เป็น uv update หรือ uv upgrade แต่ทำงานเป็นออปชันของคำสั่ง lock จึงไม่ค่อยตรงสัญชาตญาณเท่าไรสำหรับคำสั่งจัดการแพ็กเกจที่มนุษย์ใช้งาน
- เมื่อรวมกับข้อกำหนดแบบไม่มีขอบเขตบน
uv lock --upgrade จึงเท่ากับการยกทุกแพ็กเกจใน lockfile ไปเป็นเวอร์ชันล่าสุดที่สุดเท่าที่จะเป็นไปได้
- การอัปเดตนี้อาจรวมถึง dependency ที่ซ้อนลึกซึ่งเราไม่ได้รู้จักโดยตรงด้วย
- หากต้องการอัปเดตเฉพาะบางแพ็กเกจ pnpm สามารถระบุชื่อแพ็กเกจต่อกันได้ดังนี้
$ pnpm update pydantic httpx uvicorn
- แต่ใน uv ต้องใส่แฟล็ก
--upgrade-package ซ้ำสำหรับแต่ละแพ็กเกจ
$ uv lock --upgrade-package pydantic --upgrade-package httpx --upgrade-package uvicorn
- เมื่อต้องอัปหลายแพ็กเกจพร้อมกัน การใช้แฟล็กซ้ำ กลายเป็นความยุ่งยากที่ชัดเจน
แฟล็ก --bounds และการตั้งค่า
- เมื่อไม่นานมานี้ uv ได้เพิ่มออปชัน
--bounds สำหรับ uv add
$ uv add pydantic --bounds major
- คำสั่งนี้จะสร้างข้อกำหนดที่ปลอดภัยกว่า คือ
pydantic>=2.13.4,<3.0.0
--bounds major ตอนนี้ยังเป็นฟีเจอร์พรีวิว และเป็นออปชันแบบ opt-in ที่ต้องพิมพ์เองทุกครั้ง
- ต่อมาพบว่าสามารถตั้งค่าเริ่มต้นครั้งเดียวใน
pyproject.toml ได้
[tool.uv]
add-bounds = "major"
- หากใช้การตั้งค่านี้ ก็จะได้ค่าเริ่มต้นที่สมเหตุสมผลกว่าสำหรับ
uv add ในครั้งต่อ ๆ ไป โดยไม่ต้องพิมพ์ --bounds major ทุกครั้ง
- สำหรับแอปพลิเคชัน พฤติกรรมนี้ควรเป็นค่าเริ่มต้นจะดีกว่า แต่ในด้านความสะดวกใช้งานจริงก็ไม่ได้แย่อย่างที่อธิบายไว้ตอนแรกเสียทีเดียว
ความแตกต่างระหว่างแอปพลิเคชันกับไลบรารี
- คำแนะนำมาตรฐานของ Python packaging คือ ไลบรารี ที่เผยแพร่บน PyPI ไม่ควรตรึงขอบเขตบนไว้ และคำแนะนำนี้ก็สมเหตุสมผล
- หากทุกไลบรารีตรึงขอบเขตบนไว้ ต้นไม้ dependency ของผู้ใช้ปลายทางด้านล่างอาจไม่สามารถ resolve ได้
- ในทางกลับกัน แอปพลิเคชัน คือโหนดปลายสุดของกราฟ dependency และไม่มีผู้ใช้รายอื่นมา resolve ตามข้อกำหนดเหล่านั้น
- สำหรับแอปพลิเคชัน การมีขอบเขตบนไม่มีต้นทุน และช่วยป้องกันการกระโดดขึ้นเมเจอร์เวอร์ชันโดยไม่คาดคิดได้
- ขอบเขตของบทความนี้คือการบำรุงรักษาแอปพลิเคชันอย่างเว็บไซต์ บริการ หรือเครื่องมือภายในองค์กร ส่วนในการเผยแพร่ไลบรารี ค่าเริ่มต้นแบบไม่มีขอบเขตบนอาจยังสมเหตุสมผล
ส่วนที่แก้ไขแล้วและปัญหาที่ยังเหลืออยู่
- หากใช้
uv pip list --outdated ก็สามารถกรองดูเฉพาะแพ็กเกจที่ล้าสมัยได้
$ uv pip list --outdated
- ด้วยเหตุนี้ คำวิจารณ์เรื่องผลลัพธ์ที่รกเกินไปของ
uv tree --outdated --depth 1 จึงเบาลง
- ปัญหาที่ยังเหลือคือฟังก์ชันนี้ไม่ได้เป็นคำสั่งระดับบนสุด แต่ไปอยู่ใต้เนมสเปซที่เข้ากันได้กับ
pip จึงมีการค้นพบได้ยาก
- สามารถกำหนด bounds เริ่มต้นได้ด้วย
add-bounds = "major" ทำให้ภาพแบบเลือกเอาระหว่างต้องแก้ขอบเขตบนเองทุกครั้งหรือยอมรับความเสี่ยงนั้นไม่ตรงนัก
- ถึงอย่างนั้น ฟีเจอร์ดังกล่าวยังเป็นพรีวิว และในการจัดการแพ็กเกจสำหรับแอปพลิเคชันก็ยังต้องการข้อกำหนดเริ่มต้นที่ปลอดภัยกว่าและคำสั่งอัปเดตที่ตรงสัญชาตญาณกว่าเดิม
แนวทางปรับปรุงที่อยากเห็น
- ควรมีคำสั่งเฉพาะอย่าง
uv outdated ที่แสดงเฉพาะแพ็กเกจที่ล้าสมัยอย่างชัดเจน
- ควรมีคำสั่ง
update ที่ใช้งานได้คล่องตัวกว่าเดิม โดยไม่ต้องใส่แฟล็กซ้ำเมื่อต้องอัปเดตหลายแพ็กเกจ
- ข้อกำหนดเวอร์ชันโดยปริยายควรสะท้อนความคาดหวังด้านเสถียรภาพของ semantic versioning (SemVer) ได้ดีกว่านี้
- ในสภาพปัจจุบัน ยังมีภาระที่ต้องตรวจทุกบรรทัดของการเปลี่ยนแปลงใน lockfile อย่างระแวดระวัง
1 ความคิดเห็น
ความเห็นจาก Hacker News
ช่วงเวอร์ชันเริ่มต้นของ
uv addสามารถตั้งเป็น ค่าถาวร ได้ จึงไม่จำเป็นต้องส่งทุกครั้งอ้างอิง: https://docs.astral.sh/uv/reference/settings/#add-bounds
เหตุผลที่ค่าเริ่มต้นไม่ใส่ขอบเขตบนก็เพราะมันสร้างความขัดแย้งที่ไม่จำเป็นใน ecosystem มากเกินไป และตอนใช้ Poetry ก็เคยรวบรวมข้อมูลที่เกี่ยวข้องไว้เหมือนกัน: https://github.com/zanieb/poetry-relax#references
เวลาจัดการ dependency ในโปรเจกต์เว็บ ก็อยากมีขอบเขตบนเพื่อป้องกันการเปลี่ยนแปลงที่ทำให้พัง โดยตั้งอยู่บนสมมติฐานว่า dependency นั้นทำตาม SemVer
มันสนับสนุนไม่ให้ใส่ขอบเขตบนแบบป้องกันเกินเหตุ และคอย build ส่วนใหญ่ของ ecosystem ที่เคลื่อนไหวอยู่ทุกครั้งที่ออกรีลีส เพื่อหาปัญหาความเข้ากันได้จริง พร้อมส่งการแจ้งเตือนอัตโนมัติให้เจ้าของ และให้กำหนดเวลาที่ชัดเจนสำหรับการคงอยู่ในรีลีส "LTS" ถัดไป
ตอนนี้ดูเหมือนว่าตัวแก้ dependency ของ Cabal เพียงอย่างเดียวก็ค่อนข้างเสถียรแล้ว แต่การมี nightly build ครอบคลุมกว้าง ๆ พร้อมความล้มเหลวและการบล็อกที่มองเห็นได้ น่าจะช่วยให้ ecosystem ยังคงแก้ dependency ได้ต่อไป
add-boundsและคิดว่ามันมีประโยชน์กับโปรเจกต์ที่การ pin dependency แบบเป๊ะสำคัญ แต่ผู้พัฒนาที่ประสบการณ์น้อยอาจพลาดได้ โดยเฉพาะ ผลิตภัณฑ์ปลายทาง ที่ไม่ใช่ไลบรารี--native-tlsแบบถาวรไหมเพราะการตั้งค่า Zscaler ที่บริษัททำให้ UV ล้มเหลวตลอดถ้าไม่มีแฟล็กนี้
อีกอย่างก็อยากรู้ว่ามีแผนรองรับการระบุเวอร์ชัน Python ที่เข้ากันได้กับสถาปัตยกรรมเฉพาะหรือไม่ แพ็กเกจหนึ่งที่บริษัทดูแลต้องใช้ Python 32 บิต เลยต้องส่ง
--python /path/to/32bitตลอดexclude-newerในpyproject.tomlไหมพอรัน
uv runแล้วexclude-newerในpyproject.tomlจะถูกลบออกจะให้รัน
uv run —-frozenหรือuv run --exclude-newerทุกครั้งก็พอทำได้ แต่ไม่ค่อยรู้สึกว่าเป็น workflow ที่ถูกต้อง เลยสงสัยว่ามีวิธีที่เป็นธรรมเนียมกว่านี้ไหมuvถูกออกแบบมาให้ ไม่มีขอบเขตบน เพราะต้องการผลการแก้ dependency เพียงชุดเดียวnpm สามารถติดตั้งผลการแก้ dependency ที่ต่างกันในคนละส่วนของ tree ได้ แต่ใน Python ไม่มีทางเลือกนั้น Rye ก็ต้องตัดสินใจแบบเดียวกัน และตรงนี้ไม่มีทางออกที่ดีกว่า
ถ้าใส่ขอบเขตบนจริง ๆ ก็อาจทำให้เกิด tree ที่แก้ dependency ไม่ได้อีกต่อไป ในอดีต ecosystem ของแพ็กเกจ Python บางส่วนถึงขั้นต้องแจก override สำหรับแพ็กเกจเก่าที่ปล่อยออกมาโดยตั้งสมมติฐานเรื่องขอบเขตบนผิด
ณ วันนี้ เราไม่มีทางรู้ได้ว่าแพ็กเกจที่ยังไม่ถูกปล่อยออกมาในอนาคตจะเข้ากันได้กับแพ็กเกจของเราหรือไม่
uvจะบอกตอนอัปเดตว่าแพ็กเกจเข้ากันไม่ได้ และถ้าจำเป็นค่อย override เองยังดีกว่าไปเจอ error จากเวอร์ชันไม่เข้ากันตอน runtime ที่ตามแกะยาก
pyproject.tomlไม่ใช่ปัญหาที่แท้จริงปัญหาจริงคือ
uv lock —-upgradeจะ อัปเกรดทุกอย่างรวดเดียว ที่ไม่มีขอบเขตบนถ้ามีวิธีอัปเกรดแพ็กเกจโดยไม่ขยับเมเจอร์เวอร์ชัน คำสั่งนี้จะปลอดภัยขึ้นมาก
เทียบกับก่อนมี uv ถือว่าดีขึ้นมหาศาล แต่ก็คงยากที่จะดีได้อย่างสมบูรณ์ถ้าไม่ยอมให้ ecosystem ทั้งหมดมีการเปลี่ยนแปลงแบบทำให้ของเดิมพังบ้าง พอนึกถึงตอนย้ายจาก Python 2 ไป 3 ก็เดาว่าคงยังไม่มีใครอยากให้เกิดอะไรแบบนั้นในเร็ว ๆ นี้
แฟล็ก
—-boundช่วยได้ แต่ก็เป็นอีกอย่างที่ต้องพิมพ์และต้องจำถ้า uv รู้ได้ว่าโปรเจกต์นั้นไม่ใช่ไลบรารี บางทีอาจตั้งให้ใส่ขอบเขตบนเป็นค่าเริ่มต้นได้ไหม
=แล้วอย่าไว้ใจว่าอัปเดตที่ไม่ใช่เมเจอร์จะไม่พัง และ อัปเดตด้วยมือเท่านั้นแอปที่ใช้งานจริงตัวหนึ่งมี Python dependency 257 ตัว และกว่าครึ่งเป็น direct dependency
ใน
pyproject.tomlไม่ได้ใส่ขอบเขตบน แล้วรันuv lock --upgradeทุกสองสัปดาห์ผ่าน GitHub Actionsเรามี test coverage ที่ดี ถ้ามีพัง test ก็จะ fail และยังมี workflow ช่วยรีวิวด้วย AI ด้วย พอมี PR สำหรับอัปเกรด ระบบ AI จะใช้สคริปต์ Python มาสรุปรายการอัปเดตเมเจอร์/ไมเนอร์ หาลิงก์ changelog พร้อมสรุป และวิเคราะห์ความเสี่ยงของแต่ละแพ็กเกจจากวิธีที่โค้ดเบสใช้งานแพ็กเกจนั้น
โดยรวมแทบไม่เจ็บปวดเลย ไม่ต้องค่อย ๆ อัปทีละแพ็กเกจ ไม่ต้องไล่ดูแพ็กเกจเก่า หรือจัดการ dependency ที่ถูกปล่อยทิ้งไว้ กรณีที่ต้องรอให้ผู้เขียน dependency แก้ไขจนเราแก้ในโค้ดเองไม่ได้เกิดขึ้นน้อยมาก ประมาณปีละครั้ง ในช่วง 3 เดือนที่ผ่านมา มีการขยับเมเจอร์เวอร์ชัน 18 ครั้ง และมีแค่ครั้งเดียวที่ต้องแก้โค้ด
อยากทำแบบนี้กับฝั่งฟรอนต์เอนด์เหมือนกัน แต่ test ยังไม่พอจะรันแบบปลอดภัยได้ การเขียน test ฝั่งแบ็กเอนด์ทำง่ายกว่าและสำคัญกว่า จึงคิดว่าทุกโค้ดเบสควรมี ถ้ามี test แล้ว ก็แค่อัปเกรดอัตโนมัติทั้งหมดได้เลย
อย่างน้อยก็เก่งมากในการแปลงคำสั่งภาษาธรรมชาติให้เป็น test ที่ถูกต้อง
ช่วงหลังแทบไม่ได้เขียน test ด้วยมือตัวเองเลย และเมื่อก่อนเป็นเรื่องที่บ่นตลอด แต่ตอนนี้ไม่ใช่แล้ว
UV ทำอะไรให้ Python เยอะมากก็จริง แต่วันนี้ก็ต้องสู้กับมันพอสมควร
กำลังพยายามรวมการดูแลสคริปต์ที่กระจายอยู่หลาย repository และเวลาผ่านไปก็มีรูปแบบการเขียนที่แยกกันมากขึ้น
วิธีที่คิดไว้คือ
uv run --with $package main --helpและอยากให้มัน 1) ถ้าไม่มีให้ติดตั้งแล้วรันอัตโนมัติ 2) ถ้าเป็นเวอร์ชันล่าสุดอยู่แล้วก็ไม่ต้องติดตั้ง 3) ถ้าไม่ล่าสุดก็อัปเดตให้แต่ทั้งสามข้อกลับยุ่งยากกว่าที่คิด โดยพื้นฐานแล้ว
uv runจะติดตั้งใหม่ทุกครั้ง ทำให้เสียเวลา 6 วินาทีไปกับ virtual environment และการติดตั้งuvxหรือuv toolก็ไม่ได้ดีขึ้นมากนัก เพราะกลายเป็นเกิดปัญหาใหม่คือผู้ใช้จะไม่ได้รับการอัปเกรดสุดท้ายเลยต้องทำให้สคริปต์ยิง GET แบบ pagination ไปที่ CodeArtifact ถ้ามีเวอร์ชัน non-dev ที่ใหม่กว่าก็ค่อยอัปเดตแล้วรันใหม่ มันใช้งานได้ และหน่วง 200ms ก็ดีกว่า 6 วินาที แต่ก็ยังไม่ใช่ประสบการณ์ที่ต้องการ
uv run --with $package main --helpควรทำงานแบบที่ว่าไว้โดยมี overhead น้อยมาก เลยค่อนข้างสับสนมันไม่ควรติดตั้งใหม่ทุกครั้ง และ environment ของ
--withก็ควรถูกเก็บไว้ในแคช แม้ environment จะไม่ถูกแคช แต่ dependency ก็ควรถูกแคช และการติดตั้งจากแคชก็ควรเร็วมาก น่าจะต่ำกว่า 200ms ได้แน่ถ้าเปิดตัวอย่างที่ทำซ้ำปัญหาได้แบบละเอียด จะช่วยดูให้ได้
uv tool installกับuv tool upgradeดูจะเหมาะกว่าแต่ความติดขัดเล็ก ๆ แบบนี้น่าจะแก้ได้ไม่ยากนัก จึงอยากให้เปิด issue ไว้
uv run mainมันจะติดตั้ง dependency ที่ต้องใช้แบบอัตโนมัติ แคชไว้ และรันให้: https://docs.astral.sh/uv/guides/scripts/
uv tool upgradeเองก็ได้ไม่ใช่หรือไม่แน่ใจว่าเป็น use case เดียวกันเป๊ะไหม แต่เคยใช้ซิงก์ ecosystem ของ polyrepo microservices แล้วดีมาก
ค่อนข้างแปลกใจที่มีการแนะนำ
"uv tree --outdated --depth 1"เพื่อดูรายการ dependency เก่าส่วนตัวใช้
"uv pip list --outdated"มาตลอดตั้งแต่มีให้ใช้แต่ก็เห็นด้วยว่าคำสั่งที่สำคัญระดับนี้น่าจะมี subcommand ระดับบนสุด แยกออกมาต่างหาก
ยอมรับว่า
"uv pip list --outdated"ให้ output ที่ดีกว่ามาก ขอบคุณแต่การที่มีวิธีดูแพ็กเกจเก่าถึง 2 วิธีและ output ต่างกันมาก ก็ทำให้รู้สึกว่า UX มันแย่อยู่ดี
"uv tree -od1"ก็น่าจะใช้ได้เหมือนกันแต่คำวิจารณ์ที่มักมีต่อ package manager อย่าง pacman ก็คล้ายกัน คือควรมีคำสั่งที่คนเข้าใจง่ายสำหรับสิ่งที่ใช้บ่อยแบบ apt
เมื่อเทียบกับคำว่า “แย่” ในชื่อเรื่อง ตัวอย่างที่ยกมาดูเหมือนเป็นแค่การต้องใส่อาร์กิวเมนต์เพิ่มอีกไม่กี่ตัว
ชื่อที่เหมาะกว่าน่าจะประมาณ สิ่งปรับปรุงเล็ก ๆ น้อย ๆ ที่อยากให้มีใน UV
ตัว feedback เองมีประโยชน์และส่วนใหญ่ก็เห็นด้วย แต่ถ้อยคำแบบนั้นลดคุณค่าของ feedback และชวนให้คนตั้งการ์ดป้องกัน
โดยส่วนตัวก็คิดว่า command-line interface ของ uv ใช้งานจุกจิกอยู่บ้าง แต่ก็เข้าใจว่าทำไมมันถึงถูกเขียนมาแบบนี้
uvยอดเยี่ยมก็จริง แต่ปัญหาใหญ่ที่สุดของ Python packaging ตอนนี้ก็ยังเป็นเรื่องการจัดการ แพ็กเกจวิทยาศาสตร์/แมชชีนเลิร์นนิง ให้ดีถ้าอยากติดตั้ง PyTorch ก็ต้องคิดก่อนว่าเป็นเวอร์ชันอะไร ใช้ CUDA หรือไม่ ถ้าใช้ CUDA ก็ยังมีแยกตามเวอร์ชัน CUDA อีก 6 แบบ และไฟล์ wheel ก็ใหญ่เกินกว่าจะอัปขึ้น PyPI ได้ จึงต้องโหลดโดยตรง
Conda ช่วยแก้ปัญหานี้ได้เพียงบางส่วน Spack ปรับแต่งได้อย่างมากและสามารถเตรียม C/C++/Fortran dependency ที่ต้องใช้ รวมถึง compiler toolchain เพื่อดึงประสิทธิภาพสูงสุดได้ แต่ก็ผสานกับ uv และเครื่องมืออื่นได้ไม่ดีนัก เลยทำให้การนำโปรเจกต์แมชชีนเลิร์นนิงเชิงทดลองที่นักวิจัยสร้างไว้ไปสู่ production เป็นเรื่องยาก
สุดท้ายก็ย้อนกลับมาสู่สถานการณ์แบบที่พูดไว้ตอนแรก
มี feedback ที่เป็นประโยชน์เยอะ แต่ก็ปนถ้อยคำแบบคลิกเบตอยู่บ้าง
เรื่อง
pnpm outdatedดูเป็นคำขอที่สมเหตุสมผล แม้ที่ผ่านมาอาจไม่ได้ถูกพูดถึงบ่อยนัก น่าจะมาจากความต่างของวัฒนธรรมระหว่าง Python กับ JavaScript ส่วนตัวแทบไม่เคยสนใจเลยว่า dependency เก่าหรือไม่ ตราบใดที่มันไม่เปราะบางหรือไม่พัง แต่ใน ecosystem ของ JavaScript ดูเหมือนการอัปเกรดเมื่อมีโอกาสเป็นเรื่องค่อนข้างปกติ ไม่ได้บอกว่าสิ่งนี้ไม่ดี แค่ชี้ให้เห็นว่าสัญชาตญาณเรื่องควรเปิดอะไรไว้ใน command-line interface นั้นต่างกันได้มากระหว่างชุมชนโปรแกรมมิงขนาดใหญ่อย่างที่ Armin พูด พฤติกรรมเรื่องขอบเขตบนของ uv เป็นสิ่งที่ตั้งใจไว้ และมีความจำเป็นในเชิงฟังก์ชันตามวิธีที่ Python แก้ dependency นี่เป็น trade-off ที่ Python เลือกเมื่อเทียบกับภาษาอื่น แต่ก็เป็น trade-off ที่ดีตรงที่ dependency tree จะมีแต่ละ dependency เพียงชุดเดียว และทุก requirement ที่เกี่ยวข้องกันจะถูกแก้ให้ลงตัวอยู่ในนั้น
เหตุผลที่
uv lock --upgradeเป็นแบบนั้นก็เพราะมันอัปเกรด lock file ไม่ใช่ requirement ของผู้ใช้เอง ในขณะที่pnpm updateดูเหมือนจะอัปเดต requirement ของผู้ใช้ในpackage.jsonอาจฟังดูสับสนได้ แต่การวางไว้ใต้uv lockถือว่าถูกต้องกว่า ไม่เช่นนั้นก็จะมีผู้ใช้ที่งงว่าuv upgradeไม่ได้อัปเกรดในแบบที่ตัวเองคิดว่าควรเป็น ถึงอย่างนั้นก็ยังมีช่องให้ทำให้นำเสนอได้สะอาดกว่านี้ และเห็นได้ชัดว่ามีความต้องการจากผู้ใช้สำหรับ subcommand ของ uv ที่เอาไว้อัปเกรด requirement เองโดยตรงhttps://news.ycombinator.com/item?id=48230048
คำสั่ง
uv lockที่แตะเฉพาะ lock file นั้นฟังดูสมเหตุสมผล แต่ผู้ใช้มีความต้องการจริงในการอัปเกรด direct dependency และ transitive dependency สำหรับ transitive dependency ทำได้ด้วยuv lock --upgrade-packageแต่ก็ค่อนข้างยืดเยื้อ มันใช้กับ direct dependency ได้เหมือนกัน แต่จะไม่แก้pyproject.tomlและไฟล์นี้เป็นสิ่งที่นักพัฒนามองเห็นได้ชัดกว่ามากถ้าเวอร์ชันแพ็กเกจใน
uv.lockนำหน้าpyproject.tomlไปแล้วpyproject.tomlก็จะเชื่อถือได้น้อยลงในฐานะคู่มือบอกภาพรวมของ dependency จึงอยากได้คำสั่งuv upgradeที่เป็นมิตรกับดัก UX ที่ใหญ่ที่สุดของ uv เท่าที่เห็นจนถึงตอนนี้คือ
uv pipหลายโปรเจกต์ใช้ uv ได้ถูกต้องในงานพัฒนา ผ่านpyproject.tomlและuv.lockแต่กลับใช้uv pip install -r pyproject.tomlใน Dockerfile สำหรับ deploy หรือในเครื่องมือ CI ซึ่งเป็นการข้ามuv.lockไปปัญหาหนึ่งคือ coding agent เห็น
pipมากเกินไปในข้อมูลฝึกจนแนะนำแพตเทิร์นuv pipที่ไม่ดี แต่ uv เองก็ควรมีมาตรการช่วยปกป้องผู้ใช้ด้วยuv เป็นเครื่องมือที่ยอดเยี่ยมและควรถูกใช้อย่างแพร่หลายมากขึ้น: https://aleyan.com/blog/2026-why-arent-we-uv-yet
poetry updateก็อัปเดต lock file เช่นกัน ผมคิดว่าวิธีจัดองค์ประกอบของ CLI ใน uv ทำงานด้วยค่อนข้างน่ารำคาญ มันให้ความรู้สึกเหมือนออกแบบมาเพื่อความแม่นยำและเพื่อเครื่อง มากกว่าจะออกแบบมาให้เป็นมิตรกับผู้ใช้uv pip list --outdateduv upgradeอยู่ใน roadmapที่ยังไม่ได้ทำเพราะการทำให้เป็นประสบการณ์ที่ดีนั้นยากมาก มีรายละเอียดปลีกย่อยมากกว่าที่คนคิด ทีมก็เล็ก และมีลำดับความสำคัญอื่นอีกมาก
pnpm outdatedคือใช้ดูว่าเมื่อรัน"uv sync --update"หรือ"uv lock --update"แล้วอะไรบ้างที่จะถูกอัปเดตแต่อาจจะดีกว่าถ้าคำสั่งเหล่านั้นมี prompt ยืนยันมาก่อน
Pixi ใช้ uv เป็น backend และผมชอบ UI ของมันเพราะเพิ่ม task alias เพื่อแสดงรายการแพ็กเกจเก่าให้อ่านง่ายได้สะดวก
โดยเฉพาะ Pixi-diff-to-markdown ที่ช่วยให้ไล่ดูการอัปเดตแพ็กเกจอัตโนมัติใน CI ได้ง่าย
ถ้าอยากดูว่าในบรรดาแพ็กเกจเก่า มีตัวไหนบ้างที่จะถูกอัปเดต ก็สร้าง task alias แบบนี้ในโปรเจกต์ได้
pixi task add outdated "pixi update --dry-run --json | pixi exec pixi-diff-to-markdown"แล้วในโปรเจกต์ก็รัน
pixi run outdatedoutput จะเป็นตาราง Markdown ที่อ่านง่าย ซึ่งมีแพ็กเกจที่จะอัปเดต เวอร์ชันเดิม และเวอร์ชันใหม่ที่จะถูกติดตั้งด้วยคำสั่ง
pixi updateแน่นอนว่าก็แล้วแต่รสนิยมและบริบทไม่นานมานี้มีสคริปต์
envโผล่มาใน path จนไปรบกวนการใช้คำสั่ง UNIX ปกติอย่างenvพอไปดูถึงรู้ว่าโปรแกรมติดตั้งของ uv เป็นคนสร้างมันขึ้นมาตอนรันคำสั่งนี้
curl -LsSf [https://astral.sh/uv/install.sh](<https://astral.sh/uv/install.sh>) | shมันติดตั้งลงใน
$HOME/.cargo/bin/และสร้างเชลล์สคริปต์ไว้ที่$HOME/.cargo/envเพื่อเติม$HOME/.cargo/bin/ไว้ข้างหน้าถ้าPATHยังไม่มีเข้าใจได้ยากจริง ๆ ว่าทำไมนักพัฒนาบางคนถึงเขียนโค้ดที่ไปทับคำสั่ง UNIX พื้นฐานแบบนี้