1 poin oleh GN⁺ 2 jam lalu | 1 komentar | Bagikan ke WhatsApp
  • 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

 
GN⁺ 2 jam lalu
Opini Hacker News
  • Dari contohnya terlihat dipakai seperti lazy from typing import Iterator, jadi terasa seperti Python akhirnya punya lazy import
    Sepertinya aku melewatkan perubahan ini; penasaran apakah ini mulai dari Python 3.15 atau sudah ada di versi sebelumnya

    • Fitur 3.15: https://docs.python.org/3.15/whatsnew/3.15.html#whatsnew315-...
    • Aku kurang paham apa keuntungan lazy import di sini. Toh kalau nilainya dipakai dalam type hint di lingkup modul, bukankah tetap perlu import?
      Untuk itu sepertinya butuh evaluasi anotasi yang ditunda, dan setahuku itu belum aktif secara default
    • Di versi Python sebelumnya juga bisa diakali dengan mengimplementasikan def __getattr__(name: str) -> object: pada level modul
    • Ini tampaknya salah satu fitur utama Python 3.15, jadi mungkin terlewat dari tulisan ini. Di dokumen What's New juga disebut paling pertama, jadi jelas layak dianggap sebagai fitur utama
      Secara 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
    • Python sebenarnya sudah hampir sejak hari pertama memungkinkan lazy import dengan menaruh import di dalam fungsi. Sampai fungsi itu dipanggil, library-nya tidak akan di-import
  • Dengan ditambahkannya frozendict di 3.15, sekarang semua tipe JSON—yaitu array, boolean, floating-point, null, string, dan object—bisa direpresentasikan dalam bentuk immutable dan hashable
    Fitur 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-generator buatanku 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 difference
    https://en.wikipedia.org/wiki/Symmetric_difference

    • Benar juga, tapi kalau diterapkan ke Counter hasilnya jadi symmetric difference multiset, dan ini tidak punya definisi yang alami
      Kalau 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_2 sehingga terasa lebih alami, tapi tetap saja sulit melihat kegunaan nyatanya
  • Salah satu contoh Counter itu keliru. Sudah kucoba di 3.13 dan 3.15.0a
    Hasil Counter(a=3, b=1) - Counter(a=1, b=2) adalah Counter({'a': 2})

    • Aku juga melihat itu. Menurut dokumentasinya, beberapa operasi matematika disediakan untuk menggabungkan objek Counter menjadi multiset; penjumlahan dan pengurangan menambah atau mengurangi jumlah elemen yang bersesuaian, sedangkan irisan dan gabungan masing-masing mengembalikan jumlah minimum/maksimum
      Setiap 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

    • Mungkin pada awalnya sederhana, tapi aku penasaran bagaimana rencanamu soal pemeliharaan proyek-proyek itu ke depannya, terutama saat menambah fitur yang lebih kompleks
      Salah satu caranya mungkin membuat prototipe di Python lalu mengonversinya
    • Go benar-benar kurang cocok untuk komputasi ilmiah atau pekerjaan machine learning. Ekosistem library-nya belum siap, dan urusan membungkus C API juga masih lemah bahkan dengan bantuan LLM
      Coba saja menulis kode pemrosesan sinyal dengan filter, windowing, overlap, dan semacamnya; dengan library yang ada sekarang hampir tidak ada cara mudah untuk melakukannya
    • Aku terus mencari framework web Go yang komprehensif seperti Django. Kalau ada yang seperti itu, rasanya aku langsung tertarik
    • Sebenarnya aku penasaran kenapa awalnya kamu sampai memilih Python. Kalau untuk orang yang benar-benar belum bisa pemrograman, kamu akan merekomendasikan apa?
    • Menarik. Kalau boleh tahu, aku penasaran apakah itu proyek kerja atau proyek pribadi
  • 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

    • Python modern belakangan ini sangat menyenangkan kupakai, baik untuk kerjaan kantor maupun proyek pribadi
    • Siapa yang sedang membuat bahasa mirip Python yang lebih kuat, tetap mudah terhubung dengan Python, tapi bebannya lebih ringan?
  • 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, ContextDecorator berubah 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 diduga

    • Tim inti Python tampaknya menilai kecil kemungkinan ada orang yang bergantung pada perilaku lama itu: https://github.com/python/cpython/pull/136212#issuecomment-4...
    • Skenario terburuknya apa? Para developer tetap bertahan di versi Python lama karena perubahan yang tidak kompatibel? Ya mana mungkin itu pernah terjadi
  • Fitur-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