5 điểm bởi GN⁺ 2025-03-31 | 1 bình luận | Chia sẻ qua WhatsApp
  • 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

 
GN⁺ 2025-03-31
Ý 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ều
    Cá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

    • Tôi đồng ý, nhưng muốn đi xa hơn một bước
      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ạn uv.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 uv giả định này; gói uv sẽ 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.exec phát hiện cờ đó thì không làm gì là được
    • Đây không phải thứ uv phát minh ra, mà là dùng chuẩn PEP 723 giống các công cụ khác
      https://peps.python.org/pep-0723/
    • Hợp lý, nhưng tôi nghĩ việc parse và đánh giá mã dạng mệnh lệnh khó hơn và kém linh hoạt hơn nhiều so với để dữ liệu khai báo theo nguyên tắc đặc quyền tối thiểu
      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
    • Điều kiện “dù xóa chú thích thì mã vẫn phải chạy giống hệt” vẫn đúng
      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 shell
    • Hoàn toàn đồng ý, và hy vọng dạng này cuối cùng sẽ được chuẩn hóa
      Tuy 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 import lẽ ra phải truyền tải được mọi thông tin về phụ thuộc
  • Trong 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

    • Phàn nàn rằng cần uv, và nếu không có thì phải cài thủ công hoặc dùng 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
    • Để giải quyết các vấn đề kiểu này, tôi đã thử ghép gượng docker+uv shebang, và nó hoạt động ở mức nào đó
      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
    • Thường thì muốn chạy chương trình trên máy tính, trước hết cũng phải cài thứ gì đó, nên bản thân việc cài uv có vẻ không quá tệ
      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
    • Đồng ý 100%
      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
    • Bổ sung nhỏ: nhờ khử trùng lặp gói của uv, virtualenv sẽ không chiếm dung lượng trừ khi có phụ thuộc riêng biệt
  • 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

    • Đúng là vậy, nhưng vẫn còn nhiều gói PyPI chưa được đóng gói trong nixpkgs, nên đây không phải cách tiếp cận phổ dụng như uv
    • Như bài gốc cũng nói chính xác, uv sẽ cài Python tại chỗ khi cần
    • Kỹ thuật tương tự có thể áp dụng cho bất kỳ ngôn ngữ nào
      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
    • Điều kiện phải cài Nix là yêu cầu nặng hơn nhiều so với điều kiện phải có uv
    • Tôi tò mò cách làm việc tương tự bằng lệnh dựa trên flake là nix shell thay vì nix-shell
  • Như các bình luận khác, tuyên bố “self-contained” phụ thuộc vào việc uv đã được cài
    Nế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.py là xong
    Vì 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.py cũng không được
    Có 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ều

    • Tôi đang dùng một script uve tự viết khá xấu
      $ cat ~/.local/bin/uve
      #!/bin/bash
      temp=$(mktemp)
      uv export --script $1 --no-hashes > $temp
      uv run --with-requirements $temp vim $1
      unlink $temp
      Hy vọng editor sớm hỗ trợ uv python find --script
  • Trô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

    • uv có giấy phép MIT hoặc Apache-2.0
      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
    • Tôi cho rằng vụ rug pull của Anaconda nằm ở phía repository
      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
    • Tôi hiểu là nếu dự án có thỏa thuận giấy phép contributor (CLA), và buộc contributor chuyển giao bản quyền đóng góp cho chủ sở hữu dự án, thì có thể tái cấp phép
      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

    • uv nhanh đáng kinh ngạc, nên sẽ có ích
    • Với chúng tôi, rào cản là trình quét lỗ hổng SCA vẫn chưa hoạt động vớ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

  • 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.txt là tôi vẫn xoay xở được với pip+venv mặc định

    • Trong đặc tả build của Python cũng từng có kiểu diễn tiến như vậy
      Tôi nhớ là pyproject.toml xuất hiện trước thư viện tomllib
      Vì 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