- Với việc Python 3.15.0b1 đóng băng tính năng, ngoài lazy imports và profiler Tachyon, nhiều cải tiến thực dụng khác cũng đã được chốt
- TaskGroup.cancel() của
asyncio cho phép hủy nhóm tác vụ một cách gọn gàng mà không cần ngoại lệ tự định nghĩa và contextlib.suppress
- ContextDecorator được thay đổi để bao bọc toàn bộ vòng đời của hàm bất đồng bộ, generator và iterator bất đồng bộ
- Tiện ích mới của threading giúp tuần tự hóa hoặc nhân bản việc tiêu thụ iterator giữa các luồng mà vẫn giữ nguyên mức trừu tượng, không cần Queue
- Phép toán xor được thêm vào
Counter, còn json.loads hỗ trợ phân tích JSON bất biến với array_hook và frozendict
Những thay đổi ít được biết đến trong Python 3.15
- Với việc Python 3.15.0b1 đóng băng tính năng, các tính năng sẽ có mặt trong Python năm nay đã được chốt; những thay đổi lớn gồm có lazy imports và profiler Tachyon
- Python 3.15 cũng bao gồm nhiều thay đổi tính năng nhỏ mang tính thực dụng, tuy không nổi bật như các PEP lớn, với các cải tiến ở
asyncio, context manager, iterator an toàn luồng, Counter và phân tích JSON
Hủy asyncio TaskGroup
- Thay đổi cốt lõi trong
asyncio là bổ sung khả năng hủy một cách gọn gàng TaskGroup
TaskGroup là một dạng của structured concurrency, cho phép tạo nhiều công việc đồng thời một cách gọn gàng và chờ đến khi tất cả hoàn tất
async with asyncio.TaskGroup() as tg:
tg.create_task(run())
tg.create_task(run())
# Waits for all the tasks to complete
- Trước Python 3.15, để chờ tín hiệu nền rồi dừng việc thực thi
TaskGroup, cần phải phát sinh một ngoại lệ tự định nghĩa và lọc nó bằng contextlib.suppress
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ì khi có ngoại lệ xảy ra trong task group, các tác vụ khác sẽ bị hủy, ngoại lệ
Interrupt tự định nghĩa sẽ xuất hiện như một phần của ExceptionGroup, rồi được lọc bởi contextlib.suppress
- Cách
suppress hoạt động cùng ExceptionGroup được bổ sung từ Python 3.12 nhưng không được chú ý nhiều
- TaskGroup.cancel trong Python 3.15 giúp làm cùng việc đó đơn giản hơn rất nhiều
async with asyncio.TaskGroup() as tg:
tg.create_task(run())
tg.create_task(run())
if await wait_for_signal():
tg.cancel()
TaskGroup.cancel() hủy nhóm mà không phát sinh ngoại lệ, nên không còn cần kết hợp ngoại lệ riêng và suppress
Cải tiến context manager
- Từ Python 3.3, context manager đã có thể được dùng trực tiếp như một decorator
@contextmanager
def duration(message: str) -> Iterator[None]:
start = time.perf_counter()
try:
yield
finally:
print(f"{message} elapsed {time.perf_counter() - start:.2f} seconds")
@duration('workload')
def workload():
...
# Or simple as a wrapper
duration('stuff')(other_workload)(...)
- Context manager như
duration() để in ra thời gian thực thi của một khối lệnh rất tiện khi dùng như function decorator, nhưng trong hàm bất đồng bộ, generator và iterator bất đồng bộ, nó có thể không hoạt động đúng
@duration('async workload')
async def async_workload():
...
@duration('generator workload')
def workload():
while True:
yield ...
- Iterator, hàm bất đồng bộ và iterator bất đồng bộ có ngữ nghĩa khác với hàm thông thường: khi được gọi, chúng lập tức trả về lần lượt generator object, coroutine object và async generator object
- Decorator trước đây không bao quát được toàn bộ vòng đời của đối tượng được bao bọc mà kết thúc ngay lập tức, nên không thể bọc trọn thời gian thực thi thực tế
- Trong Python 3.15,
ContextDecorator được thay đổi để kiểm tra kiểu của hàm được bao bọc và để decorator phủ lên toàn bộ vòng đời của đối tượng đó
- Nhờ vậy có thể tránh được những cạm bẫy thường gặp khi dùng context manager làm decorator và dùng cú pháp gọn gàng hơn
Iterator an toàn luồng
- Iterator là một trong những mức trừu tượng cốt lõi của Python, giúp tách biệt nguồn dữ liệu và bên tiêu thụ dữ liệu để tạo cấu trúc sạch hơn
lazy from typing import Iterator
def stream_events(...) -> Iterator[str]:
while True:
yield blocking_get_event(...)
events = stream_events(...)
for event in events:
consume(event)
- Mức trừu tượng này có thể bị phá vỡ trong môi trường threading hoặc free-threading; iterator mặc định không an toàn luồng, nên giá trị có thể bị bỏ qua hoặc trạng thái nội bộ của iterator có thể bị hỏng
- threading.serialize_iterator trong Python 3.15 bọc một iterator sẵn có để tuần tự hóa việc tiêu thụ nó giữa các luồng
import threading
events = threading.serialize_iterator(stream_events(...))
with ThreadPoolExecutor() as executor:
fut1 = executor.submit(consume, events)
fut2 = executor.submit(consume, events)
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 đây, để đồng bộ việc tiêu thụ giữa các luồng, người ta chủ yếu dựa vào Queue; nhưng với các tiện ích mới này, có thể giữ nguyên mức trừu tượng iterator hiện có ngay cả trong mã đa luồng
Tính năng bổ sung
-
Phép xor cho Counter
- collections.Counter là lớp giúp đếm tần suất xuất hiện rời rạc một cách dễ dàng; nó hoạt động gần giống
dict[KeyType, int] và cung cấp nhiều phép toán hữu ích
c = Counter(a=3, b=1)
d = Counter(a=1, b=2)
print(f"{c + d = }") # add two counters together: c[x] + d[x]
print(f"{c - d = }") # subtract (keeping only positive counts)
Counter(a=4, b=3)
Counter(a=1, b=0)
Counter cũng có các phép &, | tương ứng với giao và hợp
print(f"{c & d = }") # intersection: min(c[x], d[x])
print(f"{c | d = }") # union: max(c[x], d[x])
Counter(a=1, b=1)
Counter(a=3, b=2)
Counter có thể được xem như một tập hợp các đối tượng rời rạc, và ví dụ trên có thể được hiểu như sau
{a_0, a_1, a_2, b_0} & {a_0, b_0, b_1} == {a_0, b_0}
{a_0, a_1, a_2, b_0} | {a_0, b_0, b_1} == {a_0, a_1, a_2, b_0, b_1}
- Trong Python 3.15, phép xor cũng được bổ sung
c = Counter(a=3, b=1)
d = Counter(a=1, b=2)
c ^ d == c | d - c & d == Counter(a=3, b=2) - Counter(a=1, b=1) == Counter(a=2, b=1)
{a_0, a_1, a_2, b_0} ^ {a_0, b_0, b_1} == {a_1, a_2, b_1}
- Nếu trước đây không thường dùng các phép toán tập hợp của
Counter, có thể sẽ khó nghĩ ra ngay trường hợp sử dụng cụ thể cho xor, nhưng đây là một bổ sung giúp hoàn thiện bộ phép toán
-
Đối tượng JSON bất biến
- Việc bổ sung frozendict trong Python 3.15 giúp có thể biểu diễn đầy đủ các kiểu JSON như mảng, boolean, số thực, null, chuỗi và object dưới dạng bất biến và có thể băm
- json.load và json.loads được thêm tham số
array_hook để bổ sung cho object_hook
- Khi dùng cùng
array_hook=tuple và object_hook=frozendict, có thể phân tích JSON trực tiếp thành cấu trúc bất biến
json.loads('{"a": [1, 2, 3, 4]}', array_hook=tuple, object_hook=frozendict) == frozendict({'a': (1, 2, 3, 4)})
1 bình luận
Ý kiến trên Hacker News
Nhìn ví dụ
lazy from typing import Iteratorthì tôi tự hỏi có phải cuối cùng Python cũng đã có lazy import rồi khôngCó vẻ tôi đã bỏ lỡ thay đổi này, không rõ đây có phải từ Python 3.15 hay đã có ở các bản trước
Làm vậy thì cần đánh giá trì hoãn annotation, mà theo tôi biết thì cái đó chưa được bật mặc định
def __getattr__(name: str) -> object:ở cấp moduleCá nhân tôi rất mong chờ. Ngay tuần này tôi đã thấy chỉ vì thêm import cho một module mà ứng dụng thực tế còn không dùng tới, tiến trình Python đã vượt giới hạn bộ nhớ và bị hết bộ nhớ
importbên trong hàm. Thư viện sẽ không được import cho tới khi hàm đó được gọiViệc thêm
frozendictvào 3.15 có nghĩa là giờ có thể biểu diễn mọi kiểu trong JSON — mảng, boolean, số thực dấu phẩy động, null, chuỗi, object — dưới dạng bất biến và băm đượcTính năng cuối cùng này tôi thật sự rất thích
Tôi thích việc Python 3.15 thêm công cụ nguyên thủy đồng bộ hóa Iterator: https://docs.python.org/3.15/library/threading.html#iterator...
Gói
threaded-generatortôi viết cũng làm đúng việc này bằng thread/process + generator + queue, nên có vẻ sẽ bổ sung cho nó rất tốt: https://pypi.org/project/threaded-generator/Có người nói khó nghĩ ra chỗ dùng cho các phép toán tập hợp của
Counter, nhất là xor, nhưng chỉ cần nhìn vào hiệu đối xứng là đượchttps://en.wikipedia.org/wiki/Symmetric_difference
Counterthì nó trở thành hiệu đối xứng của đa tập, mà khái niệm này không có định nghĩa tự nhiênNếu tôi hiểu đúng đề xuất thì nó được định nghĩa bằng trị tuyệt đối của chênh lệch số lượng từng phần tử, nhưng như vậy còn không thỏa tính kết hợp. Nếu chỉ xét parity thì có thể diễn giải như phép cộng trong
F_2, nghe tự nhiên hơn, nhưng tôi vẫn chưa hình dung được sẽ dùng thực tế ở đâuMột ví dụ về
Counterlà sai. Tôi đã kiểm tra ở cả 3.13 và 3.15.0aKết quả của
Counter(a=3, b=1) - Counter(a=1, b=2)làCounter({'a': 2})Counterhỗ trợ nhiều phép toán để kết hợp thành đa tập; phép cộng và trừ cộng/trừ số lượng phần tử tương ứng, còn giao và hợp trả về số lượng tối thiểu/tối đaMỗi phép toán có thể nhận đầu vào với số lượng âm, nhưng đầu ra sẽ loại bỏ các kết quả có số lượng nhỏ hơn hoặc bằng 0. Dù sao đây đúng là một Counter-example rất hay ;-)
Tôi từng cực kỳ mê Python suốt 10 năm và rất thích làm việc với nó, nhưng trong thế giới sau AI codebot, chỉ riêng năm nay tôi đã xóa hơn 100 nghìn dòng và chuyển sang ngôn ngữ nhanh hơn. Dạo này chủ yếu là chuyển sang Go
Một cách có thể là tạo prototype bằng Python rồi chuyển đổi
Thử viết mã xử lý tín hiệu có filter, windowing, overlap... thì gần như không có cách dễ dàng nào với thư viện hiện tại
Có một cuộc phỏng vấn hay về cấu trúc nội bộ và cách vận hành của Python, nhất là liên quan tới free-threading: https://alexalejandre.com/programming/interview-with-ngoldba...
Ôi Python yêu dấu của tôi. Tôi đã dùng bạn gần 15 năm. Tôi nhớ bạn, nhưng giờ không còn dùng nữa. Không phải lỗi của bạn, chỉ là cuộc sống đã thay đổi
Iterator, hàm bất đồng bộ và iterator bất đồng bộ có ngữ nghĩa khác với hàm thông thường nên trước giờ không hợp lắm với decorator. Khi được gọi, chúng lập tức trả về generator object, coroutine function hoặc async generator object, nên decorator kết thúc ngay thay vì bao trọn toàn bộ vòng đời mà nó bọc
Ở 3.15,
ContextDecoratorđược thay đổi để kiểm tra kiểu hàm mà nó bọc, nhờ đó decorator sẽ bao trùm toàn bộ vòng đời. Tôi rất thích ý tưởng này, nhưng việc thay đổi tinh vi hành vi hiện có mà không có cơ chế chọn áp dụng có vẻ khá rủi ro. Đây đúng là kiểu tình huống “sưởi ấm bằng phím cách”, tức chỉ thành vấn đề nếu ai đó cố tình dùng decorator theo cách cũ vốn đã hỏng, nhưng nếu thực sự có người làm vậy thì có thể họ sẽ vỡ bất ngờNhững tính năng nhỏ kiểu này rốt cuộc lại thường là thứ hữu ích nhất. Đặc biệt tôi muốn thử các bổ sung mới trong thư viện chuẩn này với dự án hiện tại