Python 3.15: Những tính năng "ẩn" hữu ích chưa được lên trang nhất
Python 3.15 sắp tới không chỉ mang đến những thay đổi lớn như lazy imports mà còn sở hữu nhiều cải tiến nhỏ giúp nâng cao hiệu suất lập trình. Bài viết này sẽ đi sâu vào các tính năng như hủy TaskGroup trong Asyncio, cải thiện Context Manager, hỗ trợ Iterator an toàn luồng và các bổ sung thú vị cho JSON.
Đó lại là thời điểm của năm nữa, một phiên bản mới của Python đang đến rất gần. Với việc đóng băng tính năng của Python 3.15.0b1, chúng ta đã biết những gì sẽ xuất hiện trong Python vào cuối năm nay. Có rất nhiều tính năng lớn đang được tung ra bao gồm lazy imports và tachyon profiler mà tôi đã từng đề cập trước đây.
Năm ngoái, tôi thực sự thích việc khám phá các tính năng nhỏ hơn của Python 3.14. Tôi nhận ra rằng nhiều tính năng trong số đó thú vị không kém các PEP lớn và xứng đáng nhận được nhiều sự chú ý hơn. Năm nay, tình trạng cũng không khác gì.
Hủy TaskGroup trong Asyncio
Không có nhiều thay đổi về Asyncio trong bản phát hành này. Tính năng chính ở đây là khả năng hủy một TaskGroup một cách nhẹ nhàng (gracefully).
TaskGroup là một hình thức của structured concurrency (đồng thời có cấu trúc), nó cho phép các nhà phát triển tạo ra nhiều tác vụ đồng thời một cách rõ ràng:
async with asyncio.TaskGroup() as tg:
tg.create_task(run())
tg.create_task(run())
# Chờ tất cả các tác vụ hoàn thành
Giả sử chúng ta muốn chờ trong nền một tín hiệu nào đó để làm gián đoạn việc thực thi của nhóm tác vụ, đây dường như là một điều đơn giản để thực hiện trong asyncio, nhưng trên thực tế, việc này hơi khó khăn một chút.
Cách giải quyết cũ thường involves raising an exception:
class Interrupt(Exception):
...
with suppress(Interrupt):
async with asyncio.TaskGroup() as tg:
tg.create_task(run())
tg.create_task(run())
if await wait_for_signal():
raise Interrupt()
Cách này hoạt động vì các ngoại lệ được kích hoạt trong một nhóm tác vụ sẽ gây ra việc hủy các tác vụ khác. Ngoại lệ Interrupt tùy chỉnh được nâng lên như một phần của ExceptionGroup và sau đó được lọc bởi contextlib.suppress, dẫn đến một kết thúc nhẹ nhàng.
Cách suppress hoạt động với ExceptionGroup là một tính năng khác bị bỏ quên từ phiên bản 3.12. Đây là một thay đổi mà tôi đã tình cờ biết được khi nghiên cứu bài viết này.
TaskGroup.cancel mới làm cho quá trình này trở nên dễ dàng hơn nhiều:
async with asyncio.TaskGroup() as tg:
tg.create_task(run())
tg.create_task(run())
if await wait_for_signal():
tg.cancel()
Khác với trước kia, nó đơn giản đến mức hầu như không cần giải thích. Nó đơn giản là hủy nhóm mà không kích hoạt bất kỳ ngoại lệ nào.
Cải thiện Context Manager
Viết Decorator thực sự khó một cách bất ngờ, đến mức nó đã trở thành một câu hỏi phỏng vấn kinh điển. Nhưng bạn có biết rằng context managers cũng có thể kiêm nhiệm chức năng của một decorator không?
@contextmanager
def duration(message: str) -> Iterator[None]:
start = time.perf_counter()
try:
yield
finally:
print(f"{message} elapsed {time.perf_counter() - start:.2f} seconds")
Ở đây tôi có một context manager rất phổ biến để in ra thời gian dành cho khối lệnh. Kể từ Python 3.3, chúng ta có thể sử dụng trực tiếp nó như một decorator:
@duration('workload')
def workload():
...
Tuy nhiên, dù thuận tiện, có những trường hợp nó hoàn toàn không hoạt động:
@duration('async workload')
async def async_workload():
...
@duration('generator workload')
def workload():
while True:
yield ...
Các Iterator, hàm async và async iterator không hoạt động tốt ở đây vì chúng có ngữ nghĩa khác với các hàm tiêu chuẩn. Khi bạn gọi chúng, chúng trả về ngay lập tức các đối tượng generator object, coroutine function và async generator object tương ứng. Vì vậy, decorator hoàn thành ngay lập tức thay vì bao quát toàn bộ vòng đời của đối tượng nó đang bọc.
Đây là một vấn đề đáng tiếc mà tôi đã gặp phải nhiều lần, và thường gây khó khăn cho cả các decorator bình thường. Nhưng điều này đã thay đổi trong 3.15, giờ đây ContextDecorator sẽ kiểm tra kiểu của hàm mà nó đang bọc và đảm bảo rằng decorator bao phủ toàn bộ vòng đời.
Theo ý kiến của tôi, việc này giờ đây khiến context managers trở thành cách tốt nhất để tạo ra các decorator! Nó tránh được một số lỗi phổ biến và cung cấp cú pháp sạch sẽ hơn. Tôi khuyên mọi người nên bắt đầu sử dụng theo cách này.
Iterator an toàn luồng (Thread Safe)
Iterator là một trong những nền tảng của Python hiện đại. Kiểu iterator cho phép chúng ta tách biệt nguồn dữ liệu khỏi người tiêu dùng dữ liệu như dưới đây, dẫn đến các abstraction sạch sẽ hơn:
from typing import Iterator
def stream_events(...) -> Iterator[str]:
while True:
yield blocking_get_event(...)
events = stream_events(...)
for event in events:
consume(event)
Nhưng abstraction này bị phá vỡ khi sử dụng threading hoặc free-threading. Theo mặc định, một iterator không an toàn luồng (thread-safe), do đó chúng ta có thể thấy các giá trị bị bỏ qua hoặc trạng thái nội bộ của iterator bị hỏng.
Vấn đề này được giải quyết trong 3.15 với threading.serialize_iterator, chúng ta chỉ cần bọc iterator gốc của mình bằng công cụ này và xong:
import threading
events = threading.serialize_iterator(stream_events(...))
with ThreadPoolExecutor() as executor:
fut1 = executor.submit(consume, events)
fut2 = executor.submit(consume, events)
Cũng có decorator threading.synchronized_iterator đơn giản là áp dụng threading.serialize_iterator vào kết quả của một hàm generator.
Cuối cùng, chúng ta cũng có threading.concurrent_tee mà thay vì tách các giá trị, nó sẽ nhân bản các giá trị trên nhiều iterator:
source1, source2 = threading.concurrent_tee(squares(10), n=2)
with ThreadPoolExecutor() as executor:
fut1 = executor.submit(consume, source1)
fut2 = executor.submit(consume, source2)
Trước khi các tiện ích này tồn tại, chúng ta chủ yếu dựa vào Queues để đồng bộ hóa việc tiêu thụ giữa các luồng, với việc thêm chúng vào, chúng ta có thể tránh thay đổi abstraction cho code đa luồng.
Các tính năng thưởng (Bonus Features)
Năm ngoái tôi chỉ làm nổi bật 3 tính năng, nhưng năm nay có nhiều cập nhật hơn khiến tôi thích thú. Dưới đây là 2 thay đổi nữa có thể tác động ít hơn nhưng vẫn rất thú vị.
Toán tử XOR cho Counter
collections.Counter là một lớp rất hữu ích. Nó cho phép chúng ta dễ dàng đếm tần suất xuất hiện của các sự kiện rời rạc. Nó hoạt động rất giống với dict[KeyType, int] nhưng với vô số thao tác hữu ích.
Trong 3.15, chúng ta cũng có thể thêm xor vào danh sách này:
c = Counter(a=3, b=1)
d = Counter(a=1, b=2)
c ^ d
Tôi để mục này ở phần thưởng vì tôi chưa bao giờ sử dụng các thao tác tập hợp trên Counters và tôi thấy cực kỳ khó để nghĩ ra một trường hợp sử dụng cụ thể cho xor. Nhưng tôi đánh giá cao việc các dev thêm nó để đảm bảo tính đầy đủ.
Đối tượng JSON bất biến (Immutable JSON)
Với việc thêm frozendict trong 3.15, giờ đây chúng ta có khả năng biểu diễn tất cả các loại json (array, boolean, float, null, string, object) dưới dạng bất biến (hashable).
Một thay đổi đã được thực hiện đối với json.load và json.loads để thêm tham số array_hook bổ sung cho tham số object_hook. Điều này giờ đây cho phép chúng ta phân tích cú pháp các đối tượng json trực tiếp vào dạng này:
json.loads('{"a": [1, 2, 3, 4]}', array_hook=tuple, object_hook=frozendict) == frozendict({'a': (1, 2, 3, 4)})
Bài viết liên quan

Công nghệ
Cerebras, đối tác thân thiết của OpenAI, sẵn sàng cho đợt IPO kỷ lục định giá tới 26,6 tỷ USD
04 tháng 5, 2026

Công nghệ
Cảnh sát bắt giữ nghi can được cho là "ông trùm" của trang web buôn bán ma túy Dream Market
14 tháng 5, 2026

Công nghệ
Alienware 15 mới: Dell đang làm loãng thương hiệu cao cấp vì khủng hoảng RAM?
14 tháng 5, 2026
