- 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
Hacker News yorumları
Örnekte
lazy from typing import Iteratorgibi kullanılıyor; Python’a sonunda lazy import mı geldi diye düşündümBu 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
Bunun için anotasyonların ertelenmiş değerlendirmesi gerekir ama bildiğim kadarıyla bu varsayılan olarak açık değil
def __getattr__(name: str) -> object:tanımlayarak bunun etrafından dolaşmak mümkündü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
importkoyma şeklinde lazy import yapmak mümkündü. O fonksiyon çağrılana kadar kütüphane import edilmiyordufrozendictin 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-generatorpaketi 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 yeterlihttps://en.wikipedia.org/wiki/Symmetric_difference
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ğilCounterörneklerinden biri yanlış. Hem 3.13’te hem de 3.15.0a’da doğruladımCounter(a=3, b=1) - Counter(a=1, b=2)sonucuCounter({'a': 2})oluyorCounternesnelerini 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üyorHer 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
Bir yöntem Python’da prototip geliştirip sonra dönüştürmek olabilir
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
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
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 bozulabilirBu 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