- Dengan pembekuan fitur Python 3.15.0b1, selain lazy imports dan profiler Tachyon, sejumlah peningkatan praktis juga telah dipastikan masuk
- TaskGroup.cancel() di
asyncio memungkinkan pembatalan task group secara elegan tanpa pengecualian kustom dan contextlib.suppress
- ContextDecorator diubah agar mencakup seluruh siklus hidup fungsi asinkron, generator, dan iterator asinkron
- Utilitas baru di threading membantu menserialisasi atau menggandakan konsumsi iterator antar-thread sambil mempertahankan abstraksi tanpa Queue
- Operasi xor ditambahkan ke
Counter, dan json.loads mendukung parsing JSON immutable dengan array_hook dan frozendict
Perubahan Python 3.15 yang Kurang Dikenal
- Dengan pembekuan fitur Python 3.15.0b1, fitur-fitur yang akan masuk ke Python tahun ini telah dipastikan, dengan perubahan besar seperti lazy imports dan profiler Tachyon
- Python 3.15 juga mencakup perubahan kecil namun praktis yang tidak seterlihat PEP besar, termasuk peningkatan pada
asyncio, context manager, iterator aman-thread, Counter, dan parsing JSON
Pembatalan TaskGroup di asyncio
- Sebagai perubahan inti di
asyncio, ditambahkan kemampuan untuk membatalkan secara elegan TaskGroup
TaskGroup adalah salah satu bentuk structured concurrency, yang memungkinkan pembuatan beberapa pekerjaan konkuren secara rapi dan menunggu semuanya selesai
async with asyncio.TaskGroup() as tg:
tg.create_task(run())
tg.create_task(run())
# Waits for all the tasks to complete
- Sebelum Python 3.15, untuk menunggu sinyal latar belakang lalu menghentikan eksekusi
TaskGroup, perlu melempar pengecualian kustom dan menyaringnya dengan 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()
- Pendekatan ini bekerja karena ketika pengecualian terjadi di dalam task group, task lain akan dibatalkan, lalu pengecualian
Interrupt kustom akan muncul sebagai bagian dari ExceptionGroup dan kemudian difilter oleh contextlib.suppress
- Cara kerja
suppress bersama ExceptionGroup ditambahkan di Python 3.12, tetapi tidak banyak mendapat perhatian
- TaskGroup.cancel di Python 3.15 membuat hal yang sama jauh lebih sederhana
async with asyncio.TaskGroup() as tg:
tg.create_task(run())
tg.create_task(run())
if await wait_for_signal():
tg.cancel()
- Karena
TaskGroup.cancel() membatalkan grup tanpa melempar pengecualian, kombinasi pengecualian terpisah dan suppress tidak lagi diperlukan
Peningkatan context manager
- Sejak Python 3.3, context manager juga bisa langsung digunakan sebagai 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 seperti
duration() yang mencetak waktu eksekusi blok praktis karena bisa digunakan seperti decorator fungsi, tetapi ada kasus di mana ia tidak bekerja dengan benar pada fungsi asinkron, generator, dan iterator asinkron
@duration('async workload')
async def async_workload():
...
@duration('generator workload')
def workload():
while True:
yield ...
- Iterator, fungsi asinkron, dan iterator asinkron memiliki semantik berbeda dari fungsi biasa karena saat dipanggil langsung mengembalikan objek generator, objek coroutine, atau objek generator asinkron
- Decorator lama tidak mencakup seluruh siklus hidup target yang dibungkus dan selesai seketika, sehingga gagal mencakup keseluruhan waktu eksekusi yang sebenarnya
- Di Python 3.15,
ContextDecorator berubah untuk memeriksa tipe fungsi yang dibungkus dan memastikan decorator mencakup seluruh siklus hidup target tersebut
- Ini membantu menghindari jebakan umum saat menggunakan context manager sebagai decorator dan memungkinkan sintaks yang lebih bersih
Iterator aman-thread
- Iterator adalah salah satu abstraksi inti Python yang membantu memisahkan sumber data dan konsumen data sehingga struktur program menjadi lebih rapi
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)
- Abstraksi ini bisa rusak dalam lingkungan threading atau free-threading, karena iterator bawaan tidak aman-thread sehingga nilai bisa terlewat atau status internal iterator bisa rusak
- threading.serialize_iterator di Python 3.15 membungkus iterator yang sudah ada untuk menserialisasi konsumsi antar-thread
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)
- Sebelumnya, sinkronisasi konsumsi antar-thread umumnya bergantung pada Queue, tetapi dengan utilitas baru ini, abstraksi iterator yang ada bisa tetap dipertahankan tanpa diubah bahkan dalam kode multithread
Fitur tambahan
-
Operasi xor pada Counter
- collections.Counter adalah kelas untuk menghitung frekuensi kemunculan diskret dengan mudah, bekerja mirip
dict[KeyType, int] sambil menyediakan berbagai operasi berguna
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 juga memiliki operasi & dan | yang setara dengan irisan dan gabungan
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 dapat dipandang seperti himpunan objek diskret, dan contoh di atas dapat ditafsirkan seperti berikut
{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}
- Pada Python 3.15, operasi xor ditambahkan ke sini
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}
- Jika belum sering memakai operasi himpunan pada
Counter, mungkin sulit langsung membayangkan kegunaan praktis xor, tetapi ini menambah kelengkapan operasinya
-
Objek JSON immutable
- Dengan ditambahkannya frozendict di Python 3.15, semua tipe JSON seperti array, boolean, bilangan riil, null, string, dan objek kini dapat direpresentasikan dalam bentuk immutable dan hashable
- Parameter
array_hook ditambahkan ke json.load dan json.loads untuk melengkapi object_hook yang sudah ada
- Dengan menggunakan
array_hook=tuple dan object_hook=frozendict bersama-sama, objek JSON bisa langsung di-parse menjadi struktur immutable
json.loads('{"a": [1, 2, 3, 4]}', array_hook=tuple, object_hook=frozendict) == frozendict({'a': (1, 2, 3, 4)})
1 komentar
Opini Hacker News
Dari contohnya terlihat dipakai seperti
lazy from typing import Iterator, jadi terasa seperti Python akhirnya punya lazy importSepertinya aku melewatkan perubahan ini; penasaran apakah ini mulai dari Python 3.15 atau sudah ada di versi sebelumnya
Untuk itu sepertinya butuh evaluasi anotasi yang ditunda, dan setahuku itu belum aktif secara default
def __getattr__(name: str) -> object:pada level modulSecara pribadi aku sangat menantikannya. Minggu ini saja aku melihat proses Python di aplikasi kena kehabisan memori melewati batas hanya karena ditambahkan import modul yang sebenarnya tidak dipakai
importdi dalam fungsi. Sampai fungsi itu dipanggil, library-nya tidak akan di-importDengan ditambahkannya
frozendictdi 3.15, sekarang semua tipe JSON—yaitu array, boolean, floating-point, null, string, dan object—bisa direpresentasikan dalam bentuk immutable dan hashableFitur terakhir ini benar-benar kusukai
Senang melihat Python 3.15 menambahkan primitif sinkronisasi iterator: https://docs.python.org/3.15/library/threading.html#iterator...
Paket
threaded-generatorbuatanku juga melakukan hal yang persis ini dengan thread/proses + generator + queue, jadi sepertinya akan saling melengkapi dengan baik: https://pypi.org/project/threaded-generator/Disebut sulit membayangkan kegunaan operasi himpunan pada
Counter, khususnya xor, padahal tinggal lihat symmetric differencehttps://en.wikipedia.org/wiki/Symmetric_difference
Counterhasilnya jadi symmetric difference multiset, dan ini tidak punya definisi yang alamiKalau aku memahami usulnya dengan benar, definisinya seperti nilai absolut selisih jumlah tiap elemen, tapi ini bahkan tidak memenuhi sifat asosiatif. Kalau hanya melihat paritas, itu bisa ditafsirkan sebagai penjumlahan di
F_2sehingga terasa lebih alami, tapi tetap saja sulit melihat kegunaan nyatanyaSalah satu contoh
Counteritu keliru. Sudah kucoba di 3.13 dan 3.15.0aHasil
Counter(a=3, b=1) - Counter(a=1, b=2)adalahCounter({'a': 2})Countermenjadi multiset; penjumlahan dan pengurangan menambah atau mengurangi jumlah elemen yang bersesuaian, sedangkan irisan dan gabungan masing-masing mengembalikan jumlah minimum/maksimumSetiap operasi menerima input dengan jumlah negatif, tetapi hasil akhirnya membuang elemen dengan jumlah 0 atau kurang. Bagaimanapun juga, ini benar-benar Counter-example yang keren ;-)
Selama 10 tahun aku benar-benar tenggelam dalam Python dan senang bekerja dengannya, tetapi di dunia setelah AI codebot, tahun ini saja aku sudah menghapus lebih dari 100 ribu baris dan memindahkannya ke bahasa yang lebih cepat. Akhir-akhir ini kebanyakan kupindahkan ke Go
Salah satu caranya mungkin membuat prototipe di Python lalu mengonversinya
Coba saja menulis kode pemrosesan sinyal dengan filter, windowing, overlap, dan semacamnya; dengan library yang ada sekarang hampir tidak ada cara mudah untuk melakukannya
Ada wawancara bagus tentang struktur internal dan operasional Python, khususnya terkait free-threading: https://alexalejandre.com/programming/interview-with-ngoldba...
Ah, Python cintaku. Aku memakaimu hampir 15 tahun. Aku merindukanmu, tapi sekarang aku sudah tidak memakaimu lagi. Bukan salahmu, hidup saja yang berubah
Iterator, fungsi asinkron, dan iterator asinkron punya makna berbeda dari fungsi biasa, sehingga tidak cocok dengan decorator. Saat dipanggil, masing-masing langsung mengembalikan objek generator, fungsi coroutine, atau objek generator asinkron, jadi decorator selesai seketika alih-alih mencakup seluruh siklus hidup yang dibungkusnya
Di 3.15,
ContextDecoratorberubah untuk memeriksa tipe fungsi yang dibungkus agar decorator mencakup seluruh siklus hidupnya; idenya sangat kusukai, tetapi perubahan halus pada perilaku penggunaan lama tanpa mekanisme opt-in terasa cukup berisiko. Ini memang semacam situasi “spacebar heating” di mana masalah hanya muncul jika seseorang sengaja memakai decorator dengan cara lama yang rusak, tetapi kalau memang ada yang melakukannya, hasilnya bisa rusak tanpa didugaFitur-fitur kecil seperti ini justru sering jadi yang paling berguna pada akhirnya. Khususnya, aku ingin mencoba fitur-fitur baru yang ditambahkan ke standard library di proyek yang sedang kukerjakan sekarang