- Gần đây, do xu hướng của phát triển AI, tác giả đã bắt đầu học và sử dụng Python một cách nghiêm túc, và giờ cảm thấy rất hài lòng với hệ sinh thái này
- Python đã phát triển thành một ngôn ngữ nhanh hơn và hiện đại hơn nhiều so với trước đây, và tác giả cảm nhận rõ sự tiến bộ mạnh mẽ, bao gồm cả cải thiện hiệu năng thông qua Cython
- Tác giả đang tích cực đưa các công cụ và thư viện phát triển hiện đại như uv, ruff, pytest, Pydantic vào workflow của mình để nâng cao năng suất phát triển
- Tác giả cũng áp dụng cấu trúc dự án và các phương án tự động hóa để thu hẹp khoảng cách giữa môi trường production và kiểu phát triển dựa trên Jupyter notebook/script
- Tận dụng GitHub Actions, Docker... để xây dựng CI/CD, kiểm thử và quản lý hạ tầng một cách hiệu quả
Tóm tắt I’m Switching to Python and Actually Liking It
Vì sao chuyển sang Python
- Trong môi trường phát triển lấy AI làm trung tâm, Python đã trở thành ngôn ngữ tiêu chuẩn trên thực tế
- Trước đây chỉ dùng để viết script đơn giản, nhưng gần đây tác giả bắt đầu sử dụng nghiêm túc để xây dựng các “ứng dụng thực chiến” như RAG, agent, AI tạo sinh
- Trong quá trình đó, tác giả nhận ra rằng hệ sinh thái Python đã tiến hóa rất nhiều so với trước kia
3 điểm mạnh của Python
- Hệ sinh thái thư viện và công cụ phong phú: tối ưu cho xử lý dữ liệu, phân tích, web và AI
- Cải thiện hiệu năng nhờ Cython và các công cụ tương tự: có thể tối ưu theo hướng biên dịch
- Cú pháp dễ đọc hơn: các cú pháp legacy như
__init__, __new__ được che giấu bớt và thay bằng cách viết trực quan hơn
Cấu trúc dự án (dựa trên monorepo)
- Tác giả ưa thích cấu trúc monorepo tích hợp backend và frontend
- Chọn một kho lưu trữ duy nhất vì hiệu quả quản lý mã nguồn, dễ tìm kiếm, và đơn giản hóa pipeline triển khai và kiểm thử
- Cho rằng việc chia dự án thành quá nhiều repository là dấu hiệu của over-engineering
- Ví dụ về cấu trúc dự án điển hình như sau
project/
├── .github/ : workflow CI/CD như GitHub Actions
├── .vscode/ : cấu hình môi trường VSCode
├── docs/ : tài liệu tĩnh và website dựa trên MkDocs
├── project-api/ : backend FastAPI, bao gồm dữ liệu/notebook/công cụ/mã nguồn/kiểm thử
├── project-ui/ : frontend React/Next.js
├── docker-compose.yml (chạy tích hợp)
└── Makefile và các script tự động hóa khác
- Phân tách trách nhiệm theo cách frontend lấy dữ liệu do API server xử lý thông qua các yêu cầu HTTP
- Frontend không được xử lý dữ liệu nặng mà sẽ ủy quyền các yêu cầu HTTP cho backend API viết bằng Python
- Mỗi thư mục module Python đều được chỉ định rõ ràng bằng
__init__.py
- Bên trong
project-api được tách thành các thư mục src/app, notebooks, tools, tests
Công cụ và thiết lập chính
-
uv
- Trình quản lý package Python và công cụ build hiện đại do Astral cung cấp
- Xử lý nhanh hầu hết công việc như quản lý dependency, tạo môi trường ảo, khởi tạo dự án
pyproject.toml là tệp cấu hình cốt lõi, nơi hợp nhất toàn bộ metadata và thông tin dependency
- Có thể thiết lập nhanh môi trường dự án bằng các lệnh
uv init, uv add, uv sync
-
ruff
- Linter và code formatter Python siêu nhanh
- Công cụ tích hợp
isort, flake8, autoflake...
- Dùng
ruff check, ruff format để lint và tự động sửa lỗi
- Hỗ trợ mặc định hướng dẫn phong cách mã PEP 8
-
ty
- Trình kiểm tra kiểu tĩnh cho Python do Astral tạo ra
- Kết hợp với
typing để phân tích tĩnh và ngăn lỗi từ sớm
- Dù vẫn đang ở giai đoạn đầu phát triển nhưng đã đủ ổn định để sử dụng
-
pytest
- Framework kiểm thử Python tiêu biểu cung cấp unit test và môi trường test có thể mở rộng
- Có thể tích hợp test ngay bằng quy tắc đặt tên file đơn giản và một dòng lệnh
- Tạo test dưới dạng
test_*.py rồi chạy bằng uv run pytest
- Cú pháp gọn gàng, hệ sinh thái plugin phong phú
-
Pydantic
- Thư viện kiểm chứng dữ liệu và quản lý cấu hình môi trường
- Hỗ trợ nạp cấu hình dựa trên biến môi trường
.env và kiểm tra kiểu
- Quản lý an toàn API key hay DB URL thông qua lớp
BaseSettings
-
MkDocs
- Hỗ trợ đơn giản hóa việc tạo tài liệu và website tĩnh cho dự án Python
- Có thể nhanh chóng áp dụng thiết kế đẹp theo phong cách dự án mã nguồn mở
- Cũng dễ tích hợp với GitHub Pages
-
FastAPI
- Framework xây dựng RESTful API nhanh
- Ưu điểm gồm kiểm chứng và tài liệu hóa tự động, hiệu năng cao, tích hợp Pydantic dễ dàng
- Dựa trên Starlette và Pydantic để cung cấp độ an toàn kiểu và hiệu năng cao
-
Dataclasses
- Tính năng chuẩn của Python cho phép định nghĩa lớp hướng dữ liệu một cách đơn giản
- Tự động tạo các special method, giúp giảm mạnh mã boilerplate
Quản lý phiên bản và tự động hóa
-
GitHub Actions
- Thiết lập pipeline CI riêng biệt cho
project-api và project-ui
- Cung cấp workflow tối ưu để xây dựng pipeline CI trên nhiều hệ điều hành khác nhau
- Có thể chạy kiểm thử trong môi trường Docker để test trong cùng môi trường với production
-
Dependabot
- Tự động hóa cập nhật dependency và quản lý bản vá bảo mật
-
Gitleaks
- Công cụ ngăn rò rỉ thông tin nhạy cảm (mật khẩu, API key...) bằng cách kiểm tra bảo mật trước khi commit git
-
Pre-commit Hooks
- Công cụ dùng để tự động lint, format và kiểm tra bảo mật trước khi commit
- Kết hợp với ruff, gitleaks... để duy trì tính nhất quán và chất lượng mã nguồn
Tự động hóa hạ tầng
-
Make
- Hỗ trợ workflow phát triển nhất quán với các lệnh như
make test, make infrastructure-up
- Có Makefile ở cả thư mục gốc của dự án và trong
project-api
-
Docker & Docker Compose
- Chạy tách biệt
project-api và project-ui dưới dạng container
- Có thể khởi chạy toàn bộ ứng dụng chỉ với
docker compose up --build -d
Dockerfile bao gồm việc cài uv và lệnh chạy ứng dụng FastAPI
Kết luận
- Với môi trường phát triển Python hiện đại như trên, có thể xây dựng một workflow production hiệu quả và vững chắc
- Có thể trải nghiệm rất nhiều lợi ích từ sự trưởng thành của hệ sinh thái Python và sự phát triển của công cụ trong nhiều lĩnh vực như AI, dữ liệu và phát triển web
- Có thể hiện thực hóa một văn hóa phát triển tích hợp, bao trùm từ cấu trúc monorepo, công cụ tự động hóa, linter và type checker, môi trường kiểm thử tức thời, tài liệu hóa cho tới điều phối hạ tầng
6 bình luận
Python có hệ sinh thái thư viện và framework phong phú, nhưng nhược điểm là quản lý phiên bản gói chưa tốt và xung đột xảy ra khá thường xuyên.
Xu hướng ưu và nhược điểm khá giống với Java trước đây
uvđược nhắc trong bài đúng là hàng xịn. Không chỉ nhanh mà còn quản lý phiên bản và dependency rất ổn kiểu như npm, nên mình đang dần chốt dùnguv.Nhưng mà dạo gần đây, có vẻ như nhờ uv và poetry mà phần lớn các vấn đề quản lý phiên bản và xung đột đã được giải quyết.
Liệu Python có phù hợp để bao quát cả hệ sinh thái, kể cả những phần như React không?
Việc tích hợp trực tiếp với React có những phần khó vì ngôn ngữ khác nhau, nhưng tùy theo những gì bạn muốn thì có lẽ vẫn có những phần làm được.
Cá nhân tôi nghĩ rằng phát triển frontend bằng Python là một lĩnh vực chưa thực sự phổ biến.
Ý kiến trên Hacker News
Cách hiển thị thông báo kiểu dùng OR như “không có YOUTUBE_API_KEY hoặc YOUTUBE_CHANNEL_ID” khi thiếu biến môi trường trong code là làm phiền người dùng trong tình huống chẳng cần phải dùng OR. Tốt hơn nhiều là kiểm tra riêng từng giá trị và thông báo rõ cái nào đang thiếu. Chênh lệch thời gian phát triển gần như không đáng kể, nên tôi khuyên nên làm như vậy
Đây là một nhận xét có phần soi kỹ những chi tiết nhỏ, nhưng tôi nghĩ những trường hợp như vậy rất hợp để dùng toán tử := (toán tử walrus). Ví dụ có thể viết ngay như
if not (API_KEY := os.getenv("API_KEY")):. Cá nhân tôi thì với công cụ nội bộ, tôi cứ đểos.environ["API_KEY"]tự ném raKeyError. Tôi cũng thấy như vậy là đủ rõ ràngHơn nữa, tôi nghĩ cách tốt hơn nhiều là kiểm tra từng điều kiện một, và nếu thiếu bất kỳ cái nào thì báo tất cả cùng lúc. Như vậy sẽ giảm sự phiền toái kiểu chạy chương trình vì thiếu một biến, rồi sau đó lại gặp lỗi biến khác. Tùy tình huống đôi khi khó tránh khỏi sự bất tiện, nhưng nếu có thể thì nên hiển thị một lần cho xong
Cách tốt nhất là lấy toàn bộ biến môi trường, rồi báo cáo tất cả biến nào bị thiếu trong một lượt
Cũng có thể dùng cờ boolean để cuối cùng chỉ
exit(1)một lần. Làm vậy có thể hiển thị toàn bộ biến môi trường còn thiếu trong một lầnCũng có thể kết thúc với mã 1 đồng thời in luôn thông báo như
exit("Missing ...")Nếu đang tìm công cụ tự động tạo cấu trúc dự án, tôi khuyên dùng cookiecutter. Tôi có vài template hay dùng như python-lib, click-app, datasette-plugin, llm-plugin. Có thể dùng như sau:
uvx cookiecutter gh:simonw/python-libTôi đã làm một thứ tên là baker bằng Ruby. baker không sao chép repo template, mà tạo một danh sách công việc cần làm (danh sách các bước mang tính mệnh lệnh), và có thể trộn giữa thao tác thủ công (lấy API key rồi cấu hình) với thao tác tự động (
uv initv.v.). Nó dùng cú pháp Markdown cùng với Ruby string interpolation và bash. Tôi làm nó vì quá mệt với config nền ymlDạo này công cụ đang lên là Copier. Xem chi tiết trong tài liệu Copier
Tôi lại khá thích việc tự thiết lập dự án mới. Tôi không thật sự muốn tự động hóa việc này
Thực ra tôi nghĩ mảng công cụ tự động hóa việc cấu trúc như thế này cực kỳ hợp với workflow phát triển LLM dạng agent hiện nay
Nhận xét kiểu “Python thân thiện với con người hơn vì được cài sẵn trên hầu hết hệ Unix” là một cách diễn giải hơi lạc quan. Chỉ cần vượt quá mức
import jsonlà rất nhanh rơi vào địa ngục virtualenv. Muốn chạy trên môi trường Python 3.13.x với Ubuntu 22.04 hay 24.04, Rocky 9 v.v. thì cuối cùngvenv, container, và version manager vẫn là bắt buộcNgay cả thư viện chuẩn như
import json, nếu là ngôn ngữ không có sẵn thì vẫn phải cài riêng, nhưng Python có lợi thế năng suất ban đầu cao nhờ standard library. Dĩ nhiên với dự án lớn thì chỉ standard library là không đủ, nhưng trên thực tế tôi đã triển khai nhiều đoạn code production chỉ với standard library mà không phát sinh vấn đề triển khai hay quản lý bảo mật. Việc quản lývenvcũng không còn khó như trước, và package manager cũng đã tiến bộMột trong những giả thuyết đùa mà tôi hay nói là, một nửa lý do Docker/container lan rộng nhanh như vậy là vì nó giúp vượt qua địa ngục phụ thuộc của Python. Trải nghiệm Python đầu tiên của tôi là cài một service Python lên server vào năm 2012, và đó thật sự là địa ngục phụ thuộc, lệnh
venv, thiết lập môi trường khó nhằn, rất kinh khủng. Trong môi trườngpip,brew, macOS tôi cũng chỉ loay hoay mãi, và cứ thấy Python là né. Nhưng gần đây nhờuv, tôi cảm thấy Python đã khá hơn nhiều ngay cả từ góc nhìn người mới. Chỉ vớiuv init,uv add,uv runlà đủTôi nghĩ lúc nào cũng nên dùng
virtualenv. Cuối cùng nó cũng chỉ là một thư mục, và bây giờ nếu cố cài bằngpipra toàn hệ thống thì còn có cảnh báo, nên không còn khó như xưa nữaTốt nhất là nên dùng
virtualenvhoặc container. Dù có cảm giác khó quản lý thì cũng tránh được việc cập nhật hay nâng version thư viện ảnh hưởng tới toàn bộ hệ thốngTrước đây thường có nhiều hệ thống chỉ kèm sẵn Python2, và đôi khi chính hệ thống còn phụ thuộc vào Python2 đó, nên ngược lại còn nguy hiểm hơn
Tôi thấy Python vừa dài dòng vừa thiếu thốn cùng lúc. Muốn làm việc gì đơn giản thì либо phải thêm tới 500 dependency, либо code lại phình từ vài chục đến cả trăm dòng chỉ vì việc rất nhỏ. Vì thế tôi tránh Python vì có quá nhiều công sức thừa. Với Perl tôi có thể hoàn thành nhanh hơn và gọn hơn nhiều, nên tôi thích Perl hơn. Python có cảm giác như lập trình vì lập trình hơn là để hoàn thành công việc
Tôi cũng làm nhiều dự án không có dependency. Chỉ với standard library và một file duy nhất cũng làm được rất nhiều việc. Nếu có Python thì có thể tải bằng
curlrồi chạy ngay. Ví dụ tôi có một công cụ CLI quản lý tiền bạc dài 2000 dòng là plutus, chỉ dùng khoảng 12 module chuẩn. Khoảng 25% code là phần phân tích lệnh bằngargparse. Tôi thích viết rõ ràng, kiểu mỗi tham số một dòngBạn nói Perl nhanh và mạnh hơn Python, tôi tò mò không biết có ví dụ cụ thể nào không
Python tiện ở chỗ khi lồng các cấu trúc dữ liệu thì không cần phải nghĩ nhiều là có thể dùng ngay. Có thể trộn tự do list, tuple, dictionary v.v. và truy cập bằng cú pháp thống nhất. Perl rõ ràng thông minh và thú vị hơn, nhưng chính vì vậy lại dễ làm đầu óc tôi rối hơn nên không hợp với tôi. Python thì có hơi nhạt, nhưng độ rõ ràng rất cao, đến 5 năm sau nhìn lại vẫn hiểu được
Tôi nghĩ chỉ với standard library thôi thì Python cũng đã dùng rất ổn
Tôi là người thích cấu trúc monorepo, nhưng ở một công ty trước đây, chính cách làm này đã khiến hệ thống phình ra thành một khối siêu to và nặng, đến mức không ai dám đụng vào vì sợ vô tình ảnh hưởng code của team khác. Cốt lõi vấn đề không nằm ở bản thân repo, mà chủ yếu do quản lý một
requirements.txtcho cả repo hoặc script build bị rối. Về lý thuyết, chỉ cần cập nhật dependency một lần thì toàn bộ code sẽ được an toàn với các bản vá mới nhất, nhưng thực tế không ai dám động vào. Monorepo chỉ vận hành tốt khi tổ chức có mức độ NIH rất cao (kiểu như Google, tự làm mọi thứ). Vì trải nghiệm này, tôi bắt đầu đánh giá cao hơn cấu trúc microservice, nơi mỗi service khớp với cấu trúc team trong tổ chức. Cũng đáng tham khảo Conway's lawPython là ngôn ngữ hoạt động gần nhất với đoạn pseudocode mà tôi viết ra. Mỗi chỗ mà trong đầu tôi thấy “điều này hiển nhiên rồi” thì Python cũng thật sự cung cấp một abstraction trực quan tương ứng. Tôi xuất thân từ nền tảng thiên về toán học nên điều đó làm tôi rất hài lòng. Dĩ nhiên bây giờ tôi cũng thích các ngôn ngữ khác, nhưng Python vẫn có sức hấp dẫn riêng
Tôi gần như luôn tạo cấu trúc dự án theo cùng một mẫu. Nó giống nhau đến mức rợn người. Tôi nghĩ hệ sinh thái developer Python có lẽ đang dần hội tụ về một phong cách tương tự. Trước đây tôi cứ nghĩ lựa chọn của mình là độc đáo, nhưng thấy ai cũng làm giống vậy thì lại tự hỏi ý chí tự do của mình ở đâu rồi. Nó giống hiện tượng đặt tên em bé phổ biến: thứ bạn nghĩ là độc lạ hóa ra lại là lựa chọn phổ biến đứng thứ 2
Kiểu cấu trúc này đã phổ biến trong Python từ 10 năm trước rồi. Cuối cùng có vẻ như nhiều kỹ sư lý trí sau khi suy nghĩ kỹ đều tự nhiên hội tụ về cùng một pattern
Tôi có cảm giác cái tôi của con người như một sóng lượng tử pilot-wave trải rộng trên toàn phổ, rồi từ đó chuyển thành tồn tại. becoming-being, buồn cười thật
Thấy người khác cũng bắt đầu thích Python làm tôi thấy vui. Tôi vốn thích Ruby hơn, nhưng vì yêu cầu của khách hàng nên đành phải dùng Python. Trước đây Ruby khá chậm, nhưng trong lúc miễn cưỡng học Python tôi dần quen hơn, và giờ cũng thấy khá thích. Về cách dùng Make thì tôi hơi có ý kiến khác: nếu hoàn toàn không dùng dependency thì nó chẳng khác mấy một script có câu
case... nói nửa đùa thôi, nhưng thấy thế hệ bây giờ không còn quen với Make làm tôi hơi chạnh lòng. Đúng kiểu “hồi xưa thời tôi” vậyRuby có cú pháp đẹp hơn nhiều. Python dùng thụt đầu dòng để phân biệt scope không phải gu của tôi
Ban đầu tôi khởi đầu bằng script có
case, rồi cuối cùng nó tiến hóa thành một Makefile phẳng. Makefile chuẩn hơn và dễ đọc hơn mấy script ngẫu hứngTôi đang băn khoăn nên dùng Dataclass hay Pydantic Basemodel. Nếu đã dùng Pydantic rồi thì có phải cứ thống nhất mọi thứ bằng Pydantic luôn cũng được không, và liệu còn lý do gì để dùng Dataclass nữa không
Có một bài so sánh được tổng hợp rất tốt từ dự án attrs. Đây là so sánh chính thức của attrs; dĩ nhiên có thể hơi thiên lệch một chút, nhưng tôi nghĩ cơ sở lập luận là đủ hợp lý. Và bài blog này cũng hữu ích
Dataclass không hỗ trợ kiểm chứng nested object. Vì thế với cấu trúc phẳng đơn giản để truyền tham số hàm thì dùng dataclass hợp hơn. Nó rõ ràng hơn việc nhận quá nhiều tham số trong một list
Có vấn đề suy giảm hiệu năng do kiểm chứng dữ liệu khi khởi tạo. Cũng có các lựa chọn thay thế nhẹ và nhanh hơn nhiều như msgspec
Nếu không thật sự cần kiểm chứng hay serialization thì Pydantic lại là overhead không cần thiết. Nguyên tắc của tôi là: cần serialization thì Pydantic, còn không thì dataclass
Có thể dùng trực tiếp dataclass hiện có như
TypeAdapter(MyDataclass), nên tôi cũng thấy không nhất thiết phải tạo riêng model PydanticGần đây tôi lại thấy hài lòng hơn khi chuyển sang ngôn ngữ khác thay vì Python. Suy nghĩ của tôi về Python được viết trong bài này. Nếu sau này có dịp quay lại dùng Python, tôi nhất định sẽ thử
uv,ruff,tyasyncmới là thứ cải thiện năng suất lớn nhất. Python cóasyncio, nhưng lại tồn tại nhiều cách cạnh tranh lẫn nhau nên không có chuẩn thống nhất. JS thì gom hết vào một hướng duy nhất nên tiện hơn nhiều. Gộp cả những khác biệt nhỏ lại cũng tạo thành khác biệt lớn, và ở Python tôi thấy nhiều thứ kém dễ chịu hơn như scoping bằng thụt đầu dòng, vấn đề đường dẫn tương đối trong import, còn cú pháp object của JS thì dễ chịu hơn. Xem giải thích liên quan