Python liên tục bổ sung cú pháp mới. Bài này điểm qua những thứ đã ổn định và đáng đưa vào code hằng ngày.
Toán tử hải mã
Gán giá trị ngay trong biểu thức:
# Trước
du_lieu = doc_du_lieu()
if len(du_lieu) > 100:
print(f"Quá nhiều: {len(du_lieu)}")
# Sau
if (n := len(doc_du_lieu())) > 100:
print(f"Quá nhiều: {n}")
Hữu ích nhất trong vòng lặp đọc dữ liệu:
while (dong := f.readline()):
xu_ly(dong)
while (lo := lay_lo(1000)):
xu_ly_hang_loat(lo)
Và trong comprehension khi cần dùng kết quả tính toán hai lần:
# Gọi tinh_toan hai lần cho mỗi phần tử
ket_qua = [tinh_toan(x) for x in ds if tinh_toan(x) > 0]
# Chỉ gọi một lần
ket_qua = [y for x in ds if (y := tinh_toan(x)) > 0]
Đừng lạm dụng. Nhồi quá nhiều phép gán vào một biểu thức khiến code khó đọc hơn là dài dòng một chút.
Giải nén
dau, *giua, cuoi = [1, 2, 3, 4, 5]
print(dau, giua, cuoi) # 1 [2, 3, 4] 5
# Gộp danh sách và từ điển
a = [*ds1, *ds2, 100]
b = {**cau_hinh_mac_dinh, **cau_hinh_nguoi_dung}
# Từ Python 3.9 có toán tử riêng cho dict
b = cau_hinh_mac_dinh | cau_hinh_nguoi_dung
cau_hinh |= thay_doi # cập nhật tại chỗ
def ham(*args, **kwargs): ...
tham_so = {"a": 1, "b": 2}
ham(*danh_sach, **tham_so)
Tham số chỉ nhận theo vị trí và theo tên
def ham(a, b, /, c, d, *, e, f):
...
# ^^^^ ^^^^ ^^^^
# chỉ vị trí | cả hai | chỉ tên
ham(1, 2, 3, d=4, e=5, f=6) # OK
ham(a=1, ...) # lỗi - a chỉ nhận theo vị trí
Dấu * đặc biệt hữu ích khi hàm có nhiều tham số boolean:
def xuat(du_lieu, *, sap_xep=False, loai_trung=False, nen=False):
...
xuat(ds, sap_xep=True, nen=True) # rõ ràng
xuat(ds, True, False, True) # bị chặn, và cũng không ai đọc nổi
Dấu / cho phép bạn đổi tên tham số sau này mà không phá code của người dùng — vì họ không gọi bằng tên được.
Chuỗi f nâng cao
gia_tri = 1234.5678
f"{gia_tri:,.2f}" # 1,234.57
f"{gia_tri:>15.2f}" # căn phải trong 15 ký tự
f"{gia_tri:<15.2f}" # căn trái
f"{gia_tri:^15.2f}" # căn giữa
f"{0.856:.1%}" # 85.6%
f"{255:#x}" # 0xff
f"{1_000_000:_}" # 1_000_000
# In cả tên biến - rất tiện khi gỡ lỗi
f"{gia_tri=}" # gia_tri=1234.5678
f"{gia_tri*2=:.1f}" # gia_tri*2=2469.1
# Độ rộng động
do_rong = 20
f"{ten:>{do_rong}}"
# Áp dụng repr thay vì str
f"{ten!r}"
match
def xu_ly(su_kien):
match su_kien:
case {"loai": "click", "toa_do": (x, y)}:
return f"Nhấp tại ({x}, {y})"
case {"loai": "phim", "ma": str(ma)} if len(ma) == 1:
return f"Ký tự {ma}"
case {"loai": loai, **con_lai}:
return f"{loai} với {len(con_lai)} thuộc tính"
case [dau, *phan_con_lai]:
return f"Danh sách bắt đầu bằng {dau}"
case _:
return "Không nhận dạng được"
Điểm mạnh không nằm ở việc so sánh giá trị mà ở khả năng tách cấu trúc và gán biến ngay trong lúc so khớp.
Nhóm ngoại lệ
async def chay_nhieu():
try:
async with asyncio.TaskGroup() as nhom:
nhom.create_task(tac_vu_1())
nhom.create_task(tac_vu_2())
except* ValueError as eg:
print(f"Có {len(eg.exceptions)} lỗi giá trị")
except* ConnectionError as eg:
print(f"Có {len(eg.exceptions)} lỗi kết nối")
Cú pháp except* xử lý được nhiều ngoại lệ xảy ra đồng thời — tình huống thường gặp khi chạy song song.
Ghi chú thêm thông tin cho ngoại lệ
try:
xu_ly(ban_ghi)
except Exception as e:
e.add_note(f"Xảy ra khi xử lý bản ghi {ban_ghi['id']}")
raise
Ghi chú xuất hiện trong traceback, giúp bạn biết ngay dữ liệu nào gây lỗi mà không phải ghi log riêng.
Vài thứ đáng bỏ đi
# Cũ
from typing import List, Dict, Optional, Union
def f(x: Optional[List[int]]) -> Union[str, int]: ...
# Mới
def f(x: list[int] | None) -> str | int: ...
# Cũ
"Xin chào %s, bạn %d tuổi" % (ten, tuoi)
"Xin chào {}, bạn {} tuổi".format(ten, tuoi)
# Mới
f"Xin chào {ten}, bạn {tuoi} tuổi"
# Cũ
import os
duong_dan = os.path.join("a", "b", "c.txt")
# Mới
from pathlib import Path
duong_dan = Path("a") / "b" / "c.txt"
Cách tiếp nhận cú pháp mới
Đừng vội viết lại toàn bộ dự án. Hãy áp dụng cho code mới, và chuyển dần code cũ khi bạn động vào nó vì lý do khác.
Cũng cần kiểm tra phiên bản Python tối thiểu mà dự án hỗ trợ. Dùng cú pháp của bản 3.12 trong dự án còn phải chạy trên 3.9 sẽ khiến code không nạp được — và lỗi cú pháp không được phát hiện bởi công cụ kiểm tra thông thường.
# pyproject.toml
[project]
requires-python = ">=3.11"
[tool.ruff]
target-version = "py311" # Ruff sẽ cảnh báo nếu bạn dùng cú pháp mới hơn