Soruya cevap
Python'da async def ile oluşturulan coroutine'ler, CPython bytecode seviyesinde CO_ITERABLE_COROUTINE veya yerel coroutine bayrakları tarafından yönetilen tek kullanımlık durum makineleri olarak uygulanır. Bir async fonksiyonu çağırdığınızda, hemen bir çerçeve nesnesi ve yürütme durumu içeren bir coroutine nesnesi döner; bu durum makinesi, tamamlanmak için bu durumu sürdürmekte, bu noktada iç f_lasti (son talimat) işaretçisi sona ulaşır ve çerçeve tükenmiş olarak işaretlenir. Python çalışma zamanı, yeniden girmeyi önlemek için bu tamamlanma bayrağını kontrol eder ve sonraki beklemeler gerçekleşirse RuntimeError hatası fırlatır, çünkü coroutine'ler, lineer kontrol akışı ile tekil, ayrık asenkron işlemleri temsil etmek üzere tasarlanmıştır. Tersine, üretici fonksiyonlar fabrikalardır - her çağrı, kendi bağımsız yığın çerçevesi ve talimat işaretçisi ile yeni bir PyGenObject oluşturur ve bu, fonksiyonun her biri ayrı yürütme bağlamlarını koruyan birden fazla bağımsız iteratör üretmesine olanak tanır.
Hayattaki durum
Bir geliştirme takımı, başarısız bağlantı denemelerini üstel geri ödemeyle tekrar denemesi gereken dayanıklı bir WebSocket istemcisi oluşturuyordu. İlk olarak, bir bağlantı coroutine'ini modül seviyesinde tanımladılar ve bunun tekrar deneme mantığı boyunca yeniden kullanılmaya çalıştılar.
import asyncio async def establish_connection(): return await websockets.connect("wss://api.example.com") # Modül seviyesinde enjekte etme connection_coro = establish_connection() async def retry_connect(max_attempts=3): for attempt in range(max_attempts): try: ws = await connection_coro # İkinci döngüde başarısız olur return ws except Exception: await asyncio.sleep(2 ** attempt)
İkinci döngü yinelemekte, connection_coro'yu tekrar beklemeye çalıştığında bir RuntimeError hatası ile karşılaştı, çünkü ilk başarılı bekleme coroutine nesnesini zaten tüketmişti. Takım, üç mimari çözümü değerlendirdi.
Bir yaklaşım, RuntimeError hatasını yakaladıktan sonra except bloğunda coroutine nesnesini elle yeniden inşa etmeyi içeriyordu. Teknik olarak mümkün olmasına rağmen, bu kırılgan durum yönetimi ortaya çıkardı ve kodu, durum tükenmesini istisna işleme ile algılamaya bağımlı hale getirdi, bu da anlam olarak belirsizdir ve bağlantı mantığındaki gerçek zaman hatalarını gizleyebilir.
Başka bir çözüm, establish_connection'ı __await__ uygulayan bir sınıfa dönüştürmeyi önerdi; bu, sıfırlanabilir bir awaitable sağladı. Bu bir fabrika modeli sağladı ancak gereksiz tekrarı ve karmaşıklığı artırdı, bağlantıyı kurma amacını bulanık hale getirdi ve Python çalışma zamanının zaten işlev çağrıları aracılığıyla sağladığı durum takibine manuel olarak ihtiyaç duydu.
Seçilen çözüm, her yinelemenin taze bir coroutine nesnesi almasından emin olmak için çağrı noktasını döngü içinde taşımak oldu. ws = await establish_connection() ifadesine yeniden yapılandırarak, her deneme, bağımsız kaynak yönetimi ile yeni bir durum makinesi oluşturdu. Bu, Python'un tasarım felsefesiyle uyum sağladı, burada async fonksiyonları tek seferlik hesaplamalı gelecekler için yapıcılar olarak değerlendirildi, sonuç olarak başarısız bağlantı denemelerini sonraki tekrar denemelerden doğru bir şekilde izole eden temiz, istisnasız bir tekrar deneme mantığı sağladı.
Adayların sıklıkla unutması
Bir coroutine'i bir değişkende saklamak ve onu beklemeyi unutmaktan dolayı kaynak sızıntısı neden oluşur ve close() bunu nasıl hafifletir?
Adaylar, beklenmeyen coroutine'lerin yan etkisiz bir şekilde çözüleceğini varsayıyorlar. Ancak, bir coroutine gövdesine girmiş ve bir await ifadesinde askıya alınmışsa (örneğin, bir veritabanı bağlantısını veya kilidi tutuyorsa), çerçeve bu kaynaklara referansları korur. Coroutine nesnesi üzerinde close() çağrıldığında, çerçeve üzerinden bir GeneratorExit istisnasını zorlar, bu da durum yöneticilerini (async with) ve try/finally bloklarını kaynakları hemen serbest bırakmak için tetikler. Açık bir close() olmadan, bu kaynaklar döngüsel çöp toplama çalışana kadar tutulur, bu da bağlantı havuzu tükenme senaryoları için çok geç olabilir.
inspect.iscoroutine() ve inspect.isawaitable() nasıl farklıdır ve bu ayrım genel asyncio yardımcı programları yazarken neden önemlidir?
inspect.iscoroutine() yalnızca async def fonksiyonları tarafından oluşturulan yerel coroutine nesneleri için True dönerken, inspect.isawaitable() __await__ uygulayan herhangi bir nesne için True döner; bunlar arasında coroutine'ler, görevler, gelecekler ve özelleştirilmiş awaitable'lar bulunur. Adaylar, asyncio fonksiyonlarının ensure_future() gibi herhangi bir awaitable'ı kabul ettiğini kaçırıyorlar, sadece coroutine'leri değil. iscoroutine()'u katı bir şekilde kontrol eden kütüphaneler, asyncio.Queue().get() veya özel gelecekteki nesneler gibi geçerli awaitable'ları reddeder, bu da kayda değer asenkron işlemleri planlamak için tasarlanmış genel yardımcı işlevlerde polimorfizmi kırar.
Asenkron bir üreticiyi tüketirken async for ile await arasındaki fark nedir ve neden ilki, üreticiyi kendisi yerine bir coroutine döndürmesi için __aiter__ gerektirir?
await, bir coroutine veya geleceği tamamlamaya yöneliktir ve tek bir değer dönerken, async for, bir asenkron iteratör üzerinde yineleme yapar ve her yield ifadesinde duraklar. Adaylar async for ile bir dizi coroutine'i beklemeyi karıştırıyorlar. Kritik olarak, __aiter__ doğrudan asenkron iteratör nesnesini döndürmelidir (bir awaitable değil), çünkü Python çalışma zamanı, yineleme protokolüne başlamadan önce iteratörü elde etmek için __aiter__'ı senkron olarak çağırır. __aiter__'dan bir coroutine döndürmek TypeError hatası verecektir, çünkü protokol, asenkron yineleme durum makinesini sürdürmek için iteratörün __anext__ metoduna anında erişimi gerektirir.