- Ngay cả một script nhỏ để kiểm thử API cũng cần khớp môi trường chạy vì phụ thuộc, nhưng nếu dùng shebang thực thi của uv thì có thể biến nó thành dạng chạy ngay mà không cần quy trình cài đặt
- Ví dụ
jam_users.py dùng httpx, IPython, loguru để kiểm thử endpoint /users của Go API bằng cách xóa/tạo người dùng rồi đi vào REPL
- Cách làm cũ đòi hỏi cài package toàn cục vào Python hệ thống hoặc tự chuẩn bị virtual environment, nên việc chia sẻ và chạy lại script khá bất tiện
- Khai báo
dependencies = ["ipython", "httpx", "loguru"] trong header # /// script rồi chạy uv run jam_users.py, uv sẽ xử lý môi trường cô lập và cài đặt phụ thuộc
- Thêm shebang
#!/usr/bin/env -S uv run --script và quyền thực thi, bạn có thể chạy như ./jam_users.py trên hệ Unix trong trạng thái chỉ cần đã cài uv
Khai báo phụ thuộc ngay trong script
- Ví dụ
jam_users.py chuẩn bị dữ liệu người dùng kiểm thử cho API cục bộ http://localhost:4000/v1/users
- Gửi request API bằng httpx
- Đi vào REPL của IPython để xem phản hồi và tiếp tục kiểm thử
- Ghi log xóa và tạo bằng
loguru
- Script cơ bản có luồng đặt lại danh sách người dùng rồi nạp lại dữ liệu kiểm thử
- Lấy danh sách người dùng hiện có bằng
GET /v1/users
- Gọi
DELETE /v1/users/{id} cho từng người dùng
- Gửi danh sách người dùng đã chuẩn bị dưới dạng JSON tới
POST /v1/users
- Sau đó mở REPL bằng
IPython.embed()
- Để chạy bằng
python jam_users.py, httpx, IPython, loguru phải được cài sẵn trong môi trường chạy
- Cũng có thể cài toàn cục vào Python hệ thống hoặc tạo virtual environment riêng, nhưng cả hai đều cần chuẩn bị trước khi chạy
- Cài toàn cục có thể làm Python hệ thống trở nên lộn xộn với package
- Cách dùng virtual environment buộc phải tự quản lý các bước tạo, kích hoạt, cài đặt và chạy
- Cả hai cách đều yêu cầu có Python hệ thống tương thích với các package cần thiết
Tạo script có thể chạy trực tiếp bằng uv
- Nếu đặt thẻ
# /// script của uv ở đầu script, bạn có thể khai báo phụ thuộc ngay trong file
# /// script
# dependencies = ["ipython", "httpx", "loguru"]
# ///
- Khi có header này, bạn có thể chạy bằng
uv run jam_users.py
- uv sẽ tạo virtual environment cô lập cho script
- Tải xuống và cài đặt các phụ thuộc cần thiết
- Chạy script trong ngữ cảnh của virtual environment đó
- Shebang Python thông thường
#!/usr/bin/env python khiến Python bỏ qua phần comment # /// script, nên không thể tận dụng header script của uv
- Nếu đưa lời gọi uv trực tiếp vào shebang, bạn có thể xử lý script như một file thực thi
#!/usr/bin/env -S uv run --script
# /// script
# dependencies = ["ipython", "httpx", "loguru"]
# ///
- Cờ
-S của env sẽ tách chuỗi theo sau thành các đối số riêng rồi truyền cho env
- Sau khi cấp quyền thực thi bằng
chmod +x jam_users.py, bạn có thể chạy trực tiếp như sau
./jam_users.py
- Cách này cho phép chạy script mà không cần cài phụ thuộc riêng hay quản lý virtual environment, miễn là uv đã được cài trên hệ Unix
- Khi gửi một script Python phức tạp cho người khác, cách này giúp giảm gánh nặng phải hướng dẫn dài dòng các bước chuẩn bị hệ thống trước khi chạy
1 bình luận
Ý kiến trên Hacker News
Không riêng UV mà nói chung, tôi thấy khó chịu với cách điều khiển việc thực thi mã bằng chú thích
Dùng chú thích cho chỉ thị linter hay ghi chú của lập trình viên thì ổn, nhưng nếu là dữ liệu liên quan đến cấu hình hoặc thực thi, tôi nghĩ dạng như
UV_ENV = { "dependencies": { "requests": "2.32.3", "pandas": "2.2.3" } }sẽ tốt hơn nhiềuCách này là cú pháp Python hợp lệ, dùng cấu trúc dữ liệu chuẩn thay vì tự parse chú thích tùy ý nên dễ tạo và kiểm chứng hơn, và quan trọng nhất là giữ được nguyên tắc dù xóa hết chú thích thì mã vẫn phải chạy giống hệt
Cách được đề xuất vẫn là một hằng số ma thuật không làm gì ở runtime, chỉ được parse bằng phân tích tĩnh; và thay vì được uv đọc, một công cụ khác có thể xem nó là mã không dùng đến rồi xóa mất
Chi bằng sau
import uv, gọi trực tiếp uv để bảo nó làm gì, chẳng hạnuv.exec(dependencies=["clown"], python=">=3.10")Lần chạy đầu tiên dùng bất kỳ runtime Python nào có thể tìm được gói
uvgiả định này; góiuvsẽ chuẩn bị môi trường ảo và runtime Python, rồi gắn một cờ kiểu biến môi trường vàre-exec(3)Ở runtime thứ hai,
uv.execphát hiện cờ đó thì không làm gì là đượchttps://peps.python.org/pep-0723/
Hơn nữa, bản thân dòng shebang thực ra cũng hoạt động như một chú thích
Chỉ là sau 45 năm nó đã ăn sâu đến mức mọi người không còn để ý rằng nó là chú thích của shell
Nếu đã cài cùng các phụ thuộc thì mã sẽ chạy giống hệt
Nó không hẳn thay đổi ý nghĩa của chính mã, mà thay đổi môi trường nơi mã được chạy; ở khía cạnh đó thì không khác chú thích
#!/bin/bashở đầu script shellTuy vậy, uv có lẽ không muốn chạy mã chỉ để biết các phụ thuộc, nên nó sẽ phải là một tập con rất hạn chế của cú pháp Python
Ngay từ đầu, việc cần đến thứ như thế này đã cho thấy điểm yếu của ngôn ngữ
Bản thân câu lệnh
importlẽ ra phải truyền tải được mọi thông tin về phụ thuộcTrong vài tháng gần đây, chủ đề này xuất hiện rất thường xuyên trên HN; các ví dụ gần đây gồm
https://news.ycombinator.com/item?id=43500124
https://news.ycombinator.com/item?id=42463975
Tôi thích uv, nhưng khó đồng ý với cách gọi self-contained vì hai điểm
Thứ nhất, để chạy script thì uv phải được cài sẵn
Có thể dùng shell script để kiểm tra uv đã cài chưa, nếu chưa thì cài bằng curlpipe, nhưng như vậy phần boilerplate tăng lên khá nhiều và bản thân cách curlpipe cũng không hay
Thứ hai, tự động tạo môi trường ảo đâu đó trong thư mục home không phải là self-contained thật sự
Chạy một lần rồi xóa script thì môi trường ảo đó vẫn còn, chiếm dung lượng; tôi không tìm thấy trong tài liệu uv đảm bảo rằng các môi trường ảo tạm như vậy sẽ được tự động dọn dẹp
curl | sh, là hợp lýTuy nhiên, khi các trình quản lý gói bắt đầu đưa uv vào kho của họ, chuyện này sẽ ngày càng bớt là vấn đề
Ví dụ uv đã có trong Alpine Linux và Homebrew: https://repology.org/project/uv/versions
Ngoài ra, metadata script nội tuyến là chuẩn Python
Nếu hệ thống không có uv và cũng chưa được đóng gói, nhưng có phiên bản Python phù hợp với script, thì có thể chạy bằng pipx: https://pipx.pypa.io/stable/examples/#pipx-run-examples
pipx được đóng gói rộng rãi hơn nhiều: https://repology.org/project/pipx/versions
Trên máy của một lập trình viên bất kỳ, Docker có thể phổ biến hơn uv; bài gốc cũng nói là dự án công ty nên nghe có vẻ hợp lý
Tuy nhiên hiện tại nó hoàn toàn không cache, nên mỗi lần chạy đều tải xuống, khá vụng; có lẽ có thể sửa bằng volume
Kiểu như thế này: https://hugojosefson.github.io/docker-shebang/#python
Dù vậy, nếu khi chạy lại tải về những thứ không rõ là gì từ internet, thì khó gọi là self-contained; self-contained hoàn chỉnh thật sự có lẽ gần với AppImage hơn
Dùng thứ như py2exe thì có thể tạo “script Python” self-contained
Với lập trình viên sẽ phát sinh nhiều vấn đề, nhưng với người dùng thì vấn đề được giảm thiểu
Với Nix cũng có thể dùng cùng cách này, và dòng shebang trông như sau
#! nix-shell -i python3 -p "python312.withPackages (pkgs: [ pkgs.boto3 pkgs.click ])"Làm vậy thì thứ duy nhất hệ thống cần là Nix, thậm chí không cần cài sẵn Python
Ví dụ obvious nhất là bash với mọi dependency được chỉ định, và tôi cũng từng tạo script Rust một file nhanh bằng Nix shebang
https://nixos.wiki/wiki/Nix-shell_shebang
nix shellthay vìnix-shellNhư các bình luận khác, tuyên bố “self-contained” phụ thuộc vào việc
uvđã được càiNếu thật sự muốn một script Python self-contained, đáng xem qua trình biên dịch Nuitka
Tôi đang dùng trong production cho dịch vụ gRPC mà không gặp vấn đề, chỉ cần chạy
nuitka --onefile run.pylà xongVì là compiler, binary tạo ra đôi khi còn nhanh hơn chương trình Python gốc được đóng gói bằng PyInstaller
Trên trang GitHub của tác giả có câu: “Sứ mệnh đời tôi là cố gắng cho đến khi già chết để tạo ra Python Compiler tốt nhất có thể”
https://nuitka.net/
https://github.com/kayhayen
Tôi rất thích pattern này, nhưng tiếc là chưa làm cho nó hoạt động ổn với LSP
Tôi dùng pyright trong Helix, và dù chạy editor bằng
uv run hx script.pycũng không đượcCó thể làm kiểu
uv run --with whatever-it-is-i-need hx script.py, nhưng dần dần sẽ bị lặp lại nhiềuuvetự viết khá xấu$ cat ~/.local/bin/uve#!/bin/bashtemp=$(mktemp)uv export --script $1 --no-hashes > $tempuv run --with-requirements $temp vim $1unlink $tempHy vọng editor sớm hỗ trợ
uv python find --scriptTrông khá hữu ích
Tôi tò mò liệu uv có phải lựa chọn an toàn hơn khi triển khai dài hạn các dự án dựa trên Python không
Tôi nhớ lại vụ rug pull khoảng 5 năm trước khi dùng Anaconda để quản lý dependency: sau đó quy tắc thay đổi, các khách hàng tổ chức có hơn 200 nhân viên không còn được dùng Anaconda miễn phí nữa mà phải trả giấy phép thương mại
Họ có thể ngừng phát triển hoặc chuyển công việc tương lai sang một fork có giấy phép khác, nhưng không thể hồi tố thay đổi giấy phép cũ, nên những gì đang tồn tại hiện được bảo đảm là open source
Nếu thật sự lo lắng thì có thể tạo fork và tiếp tục đồng bộ
Thực ra điều này gần như cũng áp dụng tương tự cho các dự án OSS khác, nên tôi sẽ không quá lo
Theo tôi biết, conda chưa từng là open source và đã phân phối binary
https://github.com/astral-sh/uv?tab=readme-ov-file#license
Các gói trên conda-forge vẫn có thể dùng miễn phí
uv chỉ dùng PyPI, nên nếu có vấn đề thì có thể đổi từ uv sang pip hoặc Poetry, v.v., còn các gói vẫn được lấy từ cùng một nơi
Kiểu chủ sở hữu đó rốt cuộc sẽ bị mua lại bởi người giàu tệ nhất mà bạn có thể nghĩ tới
Tôi đã lướt qua hướng dẫn đóng góp và issue của uv nhưng không thấy CLA; còn PyTorch thì có CLA ngay đầu hướng dẫn đóng góp
Dù vậy đáng ra phải có một fork cộng đồng của phiên bản FOSS cuối cùng của Anaconda
Với Redis thì chuyện đó đã xảy ra, và Redis cũng dùng CLA: https://github.com/redis/redis/blob/unstable/CONTRIBUTING.md#redis-software-grant-and-contributor-license-agreement
Tuyệt đối không nên ký CLA
Tôi nghĩ thà chỉ đóng góp cho các dự án copyleft còn hơn
Chúng ta được trả quá nhiều tiền để làm việc miễn phí
Với các tiện ích nhỏ, đây có vẻ là một phương án đóng gói tốt thay cho container hóa
Giờ tôi phải thuyết phục toàn bộ đồng nghiệp cài uv
Trông giống bundler/inline bên Ruby
Rất vui vì Python cũng có thứ tương tự, và thực tế nó rất tiện
https://bundler.io/guides/bundler_in_a_single_file_ruby_script.html
Không biết có ai đã làm cho thứ này chạy được trên Windows chưa
Tôi muốn dùng mẹo này cho một công cụ dành cho mod game đang làm, nhưng không làm cho mẹo shebang hoạt động được
$> uv init --script .py$> uv add --script .py ...$> uv add --script .py --dev ...$> uv run .pyHy vọng là hữu ích
Nguồn: https://docs.astral.sh/uv/guides/scripts/
.pypy launcher hỗ trợ dòng shebang
Một bài blog về cùng chủ đề đăng vài ngày trước cũng đã đề cập đến việc này, và theo đó thì nên bỏ
-S: https://thisdavej.com/share-python-scripts-like-a-pro-uv-and-pep-723-for-easy-deployment/https://news.ycombinator.com/item?id=43500124
Tôi chưa tự thử, thay vào đó đã đổi liên kết tệp để mọi tệp
.pymặc định được mở bằnguv runhttps://docs.python.org/3/using/windows.html#python-launcher-for-windows
https://peps.python.org/pep-0397/
https://pyinstaller.org
https://onor.io/2025/01/more-scripting-with-racket.html
Trường hợp sử dụng này khiến tôi thích uv, nhưng tôi cảm thấy việc một PEP chính thức và rất hữu ích lại không được công cụ Python chính thức hỗ trợ là trái với Zen of Python
Theo góc nhìn của tôi, đây là lần đầu tiên Python không còn “batteries included”
Giờ hệ thống của tôi cũng có tới hai trình quản lý phụ thuộc Python
Tôi biết có rất nhiều điều để nói về quản lý phụ thuộc trong Python, nhưng trong vài năm qua, chỉ cần dự án có
requirements.txtlà tôi vẫn xoay xở được vớipip+venvmặc địnhTôi nhớ là
pyproject.tomlxuất hiện trước thư việntomllibVì vậy trong vài phiên bản, phải chỉ định module bằng một ngôn ngữ mà Python mặc định không đọc được
Tình huống đó còn tệ hơn việc có một cách mặc định chứa metadata không được dùng đến
Vì ngay từ đầu nó là metadata; nếu không thì đã chỉ là cú pháp Python rồi