1 puan yazan GN⁺ 2 시간 전 | 1 yorum | WhatsApp'ta paylaş
  • Python 3.15.0b1 özellik dondurma aşamasıyla birlikte, gecikmeli importlar ve Tachyon profiler dışında pratik iyileştirmeler de kesinleşti
  • asyncio içindeki TaskGroup.cancel(), özel istisnalar ve contextlib.suppress olmadan görev grubunu zarif biçimde iptal ediyor
  • ContextDecorator, artık asenkron fonksiyonların, jeneratörlerin ve asenkron yineleyicilerin tüm yaşam döngüsünü kapsayacak şekilde değiştirildi
  • threading için gelen yeni yardımcılar, yineleyici tüketimini thread'ler arasında serileştirmeyi veya kopyalamayı sağlayarak Queue kullanmadan soyutlamayı koruyor
  • Counter için xor işlemi eklendi; json.loads ise array_hook ve frozendict ile değiştirilemez JSON ayrıştırmayı destekliyor

Python 3.15'te daha az bilinen değişiklikler

  • Python 3.15.0b1 ile özellikler donduruldu ve bu yıl Python'a girecek yenilikler netleşti; büyük değişiklikler arasında gecikmeli importlar ve Tachyon profiler bulunuyor
  • Python 3.15, büyük PEP'ler kadar dikkat çekmeyen ama pratik olan küçük özellik değişiklikleri de içeriyor; asyncio, context manager'lar, thread-safe yineleyiciler, Counter ve JSON ayrıştırma tarafında iyileştirmeler var

asyncio TaskGroup iptali

  • asyncio tarafındaki temel değişikliklerden biri, TaskGroup'u zarif biçimde iptal etme özelliğinin eklenmesi
  • TaskGroup, structured concurrency'nin bir biçimi olarak birden çok eşzamanlı işi düzenli şekilde oluşturup hepsi tamamlanana kadar beklemeyi sağlıyor
async with asyncio.TaskGroup() as tg:
    tg.create_task(run())
    tg.create_task(run())




# Waits for all the tasks to complete
  • Python 3.15 öncesinde arka plandaki bir sinyali bekleyip TaskGroup çalışmasını durdurmak için özel bir istisna fırlatıp bunu contextlib.suppress ile filtrelemek gerekiyordu
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()
  • Bu yöntem, görev grubu içinde bir istisna oluştuğunda diğer görevlerin iptal edilmesi ve özel Interrupt istisnasının bir ExceptionGroup parçası olarak ortaya çıktıktan sonra contextlib.suppress tarafından filtrelenmesi sayesinde çalışıyordu
  • ExceptionGroup ile çalışan suppress davranışı Python 3.12'de eklenmişti, ancak çok fazla dikkat çekmemişti
  • Python 3.15'te gelen TaskGroup.cancel, aynı işi çok daha basit hale getiriyor
async with asyncio.TaskGroup() as tg:
    tg.create_task(run())
    tg.create_task(run())

    if await wait_for_signal():
        tg.cancel()
  • TaskGroup.cancel(), istisna fırlatmadan grubu iptal ettiği için ayrı istisna ve suppress kombinasyonuna ihtiyaç bırakmıyor

Context manager iyileştirmeleri

  • Context manager'lar Python 3.3'ten beri doğrudan dekoratör olarak da kullanılabiliyordu
@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)(...)
  • duration() gibi bir kod bloğunun çalışma süresini yazdıran context manager'ları fonksiyon dekoratörü gibi kullanmak pratik olsa da, asenkron fonksiyonlar, jeneratörler ve asenkron yineleyicilerde doğru çalışmadığı durumlar olabiliyordu
@duration('async workload')
async def async_workload():
    ...

@duration('generator workload')
def workload():
    while True:
        yield ...
  • Yineleyiciler, asenkron fonksiyonlar ve asenkron yineleyiciler, normal fonksiyonlardan anlamsal olarak farklıdır; çağrıldıklarında hemen sırasıyla jeneratör nesnesi, coroutine nesnesi ve asenkron jeneratör nesnesi döndürürler
  • Eski dekoratör davranışı, sardığı hedefin tüm yaşam döngüsünü kapsayamıyor ve hemen tamamlanıyordu; bu yüzden gerçek çalışma süresinin tamamını kuşatamıyordu
  • Python 3.15'te ContextDecorator, sardığı fonksiyonun türünü kontrol edecek ve dekoratörün ilgili hedefin tüm yaşam döngüsünü kapsamasını sağlayacak şekilde değiştirildi
  • Böylece context manager'ları dekoratör olarak kullanırken ortaya çıkan yaygın tuzaklar önlenebiliyor ve daha temiz bir söz dizimi kullanılabiliyor

Thread-safe yineleyiciler

  • Yineleyiciler Python'un temel soyutlamalarından biridir; veri kaynağı ile veri tüketicisini ayırarak daha temiz bir yapı kurmayı sağlar
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)
  • Ancak bu soyutlama threading veya free-threading ortamlarında bozulabilir; varsayılan yineleyiciler thread-safe değildir ve değerler atlanabilir ya da iç yineleyici durumu bozulabilir
  • Python 3.15'teki threading.serialize_iterator, mevcut bir yineleyiciyi sararak thread'ler arasındaki tüketimi serileştirir
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)
  • Daha önce thread'ler arası tüketimi senkronize etmek için çoğunlukla Queue'ya başvuruluyordu; ancak yeni yardımcılarla çok thread'li kodda da mevcut yineleyici soyutlaması değiştirilmeden korunabiliyor

Ek özellikler

  • Counter xor işlemi

    • collections.Counter, ayrık olayların frekansını kolayca saymayı sağlayan bir sınıftır; dict[KeyType, int] benzeri davranırken birçok yararlı işlem de sunar
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, kesişim ve birleşime karşılık gelen &, | işlemlerine de sahiptir
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, ayrık nesnelerden oluşan bir küme gibi düşünülebilir ve örnekler aşağıdaki gibi yorumlanabilir
{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}
  • Python 3.15 ile buna bir de xor işlemi eklendi
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}
  • Counter'ın küme işlemlerini sık kullanmadıysanız xor'un somut kullanım alanını düşünmek zor olabilir, ancak işlem bütünlüğü açısından eklenmiş bir özellik
  • Değiştirilemez JSON nesneleri

    • Python 3.15'e frozendict eklenmesiyle, dizi, boolean, kayan noktalı sayı, null, string ve nesne gibi tüm JSON türleri değiştirilemez ve hashlenebilir biçimde temsil edilebiliyor
    • json.load ve json.loads için array_hook parametresi eklendi; bu, mevcut object_hooku tamamlıyor
    • array_hook=tuple ve object_hook=frozendict birlikte kullanıldığında, JSON nesneleri doğrudan değiştirilemez yapılara ayrıştırılabiliyor
json.loads('{"a": [1, 2, 3, 4]}', array_hook=tuple, object_hook=frozendict) == frozendict({'a': (1, 2, 3, 4)})

1 yorum

 
GN⁺ 2 시간 전
Hacker News yorumları
  • Örnekte lazy from typing import Iterator gibi kullanılıyor; Python’a sonunda lazy import mı geldi diye düşündüm
    Bu değişikliği kaçırmış gibiyim, bunun da Python 3.15 ile mi geldiğini yoksa önceki sürümlerde de var olup olmadığını merak ediyorum

    • 3.15 özelliği: https://docs.python.org/3.15/whatsnew/3.15.html#whatsnew315-...
    • Burada lazy importun ne avantaj sağladığını tam anlamıyorum. Sonuçta modül kapsamındaki tip ipuçlarında o değeri kullanırsan import gerekmiyor mu?
      Bunun için anotasyonların ertelenmiş değerlendirmesi gerekir ama bildiğim kadarıyla bu varsayılan olarak açık değil
    • Önceki Python sürümlerinde de modül seviyesinde def __getattr__(name: str) -> object: tanımlayarak bunun etrafından dolaşmak mümkündü
    • Bu, Python 3.15’in öne çıkan özelliklerinden biri, o yüzden bu yazıda eksik kalmış gibi. What's New belgesinde de ilk sırada geçtiği için kesinlikle ana özelliklerden biri sayılabilir
      Ben şahsen gerçekten heyecanlıyım. Daha bu hafta bile, uygulamada fiilen kullanılmayan bir modül importu eklendi diye Python sürecinin bellek sınırını aşıp yetersiz bellek hatası verdiğini gördüm
    • Python’da neredeyse ilk günden beri, fonksiyon içine import koyma şeklinde lazy import yapmak mümkündü. O fonksiyon çağrılana kadar kütüphane import edilmiyordu
  • frozendictin 3.15’e eklenmesiyle artık JSON’daki tüm tipleri, yani diziler, booleanlar, kayan noktalı sayılar, null, stringler ve nesneleri değiştirilemez ve hashlenebilir biçimde ifade etmek mümkün oldu
    Özellikle bu son özellik çok hoşuma gitti

  • Python 3.15’e Iterator senkronizasyon ilkel araçları eklenmiş olması güzel: https://docs.python.org/3.15/library/threading.html#iterator...
    Yazdığım threaded-generator paketi de thread/process + generator + queue ile tam olarak bunu yapıyor; bunu güzel tamamlayacak gibi duruyor: https://pypi.org/project/threaded-generator/

  • Counterın küme işlemlerinde özellikle xor’un kullanım alanını düşünmenin zor olduğu söylenmiş ama, simetrik farkı düşünmek yeterli
    https://en.wikipedia.org/wiki/Symmetric_difference

    • Doğru ama Countera uygulandığında bu, çoklu kümenin simetrik farkı oluyor ve bunun doğal bir tanımı yok
      Öneriyi doğru anladıysam, her elemanın sayısı arasındaki farkın mutlak değeriyle tanımlanıyor gibi ama bu durumda birleşme özelliği de sağlanmıyor. Sadece pariteye bakılsa F_2 üzerindeki toplama olarak yorumlanabilir ve daha doğal olurdu, ama yine de pratikte nerede kullanılacağı pek net değil
  • Counter örneklerinden biri yanlış. Hem 3.13’te hem de 3.15.0a’da doğruladım
    Counter(a=3, b=1) - Counter(a=1, b=2) sonucu Counter({'a': 2}) oluyor

    • Ben de onu gördüm. Belgelere göre Counter nesnelerini birleştirip çoklu küme oluşturmak için çeşitli matematiksel işlemler sunuluyor; toplama ve çıkarma karşılık gelen eleman sayılarını topluyor ya da çıkarıyor, kesişim ve birleşim ise sırasıyla minimum/maksimum sayıları döndürüyor
      Her işlem negatif sayılı girdileri kabul edebiliyor ama çıktıda sayısı 0 ya da daha düşük olan sonuçlar dışarıda bırakılıyor. Her hâlükârda güzel bir Counter-example ;-)
  • 10 yıl boyunca Python’a gerçekten tutkuyla bağlıydım ve onunla çalışmak keyifliydi ama AI codebot sonrası dünyada, sadece bu yıl içinde 100 binden fazla satırı silip daha hızlı dillere taşıdım. Bu aralar en çok Go’ya geçiriyorum

    • Başta basit olabilir ama ileride o projelerin bakımı, özellikle de daha karmaşık özellikler eklemek gerektiğinde bunu nasıl yapmayı düşündüğünü merak ediyorum
      Bir yöntem Python’da prototip geliştirip sonra dönüştürmek olabilir
    • Go, bilimsel hesaplama ya da makine öğrenmesi işleri için gerçekten zayıf. Kütüphane ekosistemi yeterli değil ve C API sarmalama tarafı da LLM yardımıyla bile güçlü sayılmaz
      Filtreleme, pencereleme, overlap gibi şeyler içeren sinyal işleme kodu yazmaya kalkınca, mevcut kütüphanelerle bunu kolay yapmanın neredeyse yolu yok
    • Go için Django benzeri kapsamlı bir web framework aramaya devam ediyorum. Böyle bir şey çıksa hemen ilgimi çekerdi
    • En başta neden Python kullanmaya başladığını merak ediyorum. Programlamayı hiç bilmeyen birine ne önerirdin?
    • İlginçmiş. Sakıncası yoksa bunun iş projesi mi yoksa kişisel proje mi olduğunu merak ediyorum
  • Python’ın iç yapısı ve işleyişi hakkında, özellikle free-threading ile ilgili güzel bir röportaj var: https://alexalejandre.com/programming/interview-with-ngoldba...

  • Ah, sevgili Python’um. Seni neredeyse 15 yıldır kullandım. Özlüyorum ama artık kullanmıyorum. Senin suçun değil, hayat değişti

    • Günümüzün modern Pythonı ile hem işte hem kişisel projelerde çalışmak gerçekten keyifli
    • Python’la iyi entegre olan ama daha az yük taşıyan, daha güçlü bir Python benzeri dil üzerinde kim çalışıyor?
  • Iteratorler, async fonksiyonlar ve async iteratorler, anlam olarak normal fonksiyonlardan farklı olduğu için dekoratörlerle pek uyumlu değildi. Çağrıldıklarında sırasıyla anında generator nesnesi, coroutine fonksiyonu ve async generator nesnesi döndürüyorlar; bu yüzden dekoratör, sardığı şeyin tüm yaşam döngüsünü değil, yalnızca ilk anı kapsayıp hemen bitmiş oluyordu
    3.15’te ContextDecorator, sardığı fonksiyonun türünü kontrol edip dekoratörün tüm yaşam döngüsünü kapsamasını sağlayacak şekilde değişiyor; fikir çok hoşuma gidiyor ama isteğe bağlı uygulama mekanizması olmadan mevcut kullanımların davranışını ince şekilde değiştirmesi epey riskli görünüyor. Birinin özellikle eski, bozuk davranışı hedefleyerek dekoratör kullanmış olması gerekirdi; bu biraz “spacebar heating” gibi bir durum ama gerçekten böyle yapıldıysa beklenmedik şekilde bozulabilir

    • Python çekirdek ekibi, eski davranışa bağımlı kişilerin olma ihtimalini düşük görüyor gibi: https://github.com/python/cpython/pull/136212#issuecomment-4...
    • En kötü ne olabilir ki? Uyumsuz bir değişiklik yüzünden geliştiricilerin eski Python sürümlerini kullanmaya devam etmesi mi? Sanki bu hiç olmuyormuş gibi
  • Bu tür küçük özellikler sonunda en kullanışlı olanlar hâline geliyor. Özellikle şu anki projede yeni standart kütüphane eklemelerini denemek istiyorum