Python jeneratörleri, çağrılar arasında çalışma durumlarını koruyan askıya alınmış çerçeve nesneleri (PyFrameObject) olarak uygulanır. send(value) çağrısı yapıldığında, CPython'ın dahili gen_send_ex() fonksiyonu bu değeri jeneratörün değer yığınında iteratif olarak iterek yield ifadesinin bu değeri çıkardığı ve çağırana geri döndüğü noktayı oluşturur. Bu, jeneratörün başlangıç durumundan (f_lasti == -1) ilk yield ifadesine ilave olarak None gönderen ilk next() çağrısından farklıdır. Eğer jeneratör ilk defa değer sağlamadan önce None olmayan bir değer ile send() çağrısı yapılırsa, CPython bir TypeError yükseltir, çünkü jeneratör çerçevesinin bu değeri alacak bir yığın pozisyonu yoktur. Bu mimari ayrım, iki yönlü iletişimin yalnızca jeneratör ilk askıya alma noktasına ulaştıktan sonra başladığını garanti eder.
Yüksek frekanslı piyasa veri akışlarını işlemek için geri basınç farkındalığına sahip bir veri boru hattı uygulamak zorundaydık; akıştaki tüketicilerin yukarı akıştaki üreticilere veri akışını yavaşlatma veya yeniden başlatma sinyalleri verebilmeleri gerekiyordu, böylece mesaj kaybı veya bellek tükenmesi olmadan. Birincil olarak, boru hattının aşamaları arasında sınırlı queue.Queue örneği ile threading kullanmayı düşünülen bir yaklaşımdı. Bu, tanıdık bloklama semantiklerini ve iplik güvenliğini sağlasa da, ciddi GIL çekişmesi ve bağlam değiştirme yükü ile başa çıkmadı. Yüksek verimlilikte koordine olmanın yalnızca %15 CPU tükettiği kaydedildi.
Başka bir alternatif, asyncio korotinlerine ve async/await sözdizimine geçmeyi içeriyordu. Bu, GIL çekişmesini ortadan kaldıracaktı fakat senkron sayısal analiz kütüphanemizi tam anlamıyla yazmak zorunda kalmamızı gerektirecekti; bu da birçok satır iş mantığını etkileyecek ve eski C uzantılarıyla uyumluluk sorunlarına yol açacaktı.
Sonuçta, send() kullanarak yukarı akışta "talep kredileri" iletme amaçlı, jeneratör bazlı işbirlikçi çoklu görev yaklaşımını seçtik. Bu çözüm, GIL yükünü tamamen ortadan kaldırdı, kütüphanelerin yeniden yazılmasını gerektirmedi çünkü jeneratörler senkron kodda çalışabiliyor ve akışın duraklama noktalarını açıkça demand = (yield data_chunk) desenlerinde kontrol etmemize imkan tanıdı; böylece aşağı akıştaki tüketiciler, sıfır değerler göndererek yukarı akış üretimini derhal durdurabiliyordu.
Sonuç, kuyruk yaklaşımına göre bellek kullanımında %40 azalma, gecikmenin 5 milisaniyenin altında stabilize olması ve kod tabanının belirgin askıya alma noktalarını belirterek okunabilir olduğunu korumasıydı.
Yeni oluşturulmuş bir jeneratörde send()'in None olmayan bir değeri çağırmasının neden TypeError yükselttiğini ve bu kısıtlamanın jeneratör protokolünü nasıl zorladığını açıklayabilir misiniz?
Bir jeneratör ilk kez oluşturulduğunda, çerçeve işaretçisi f_lasti -1 olarak tanımlanır; bu, hiçbir byte kodunun çalışmadığını gösterir. CPython yorumlayıcısı, send() çağrıldığında jeneratörün başlatılmadığını kontrol eder; eğer gönderilen değer None değilse, yield ifadesi henüz ulaşılmadığı için bu bir TypeError yükseltir; çünkü değeri almak için bir yığın alanı sağlanmamıştır. Bu zorlamanın sağlanması, jeneratör başlatma mantığının tamamlanmasını garanti eder, iki yönlü iletişimin başlamasından önce, değerlerin yalnızca belirgin yield askıya alma noktasında akmasına olanak tanır.
generator.close()'un jeneratör içindeki temizleme kodunun çalışmasını nasıl sağladığını ve GeneratorExit istisnasını sıradan istisnalardan ayıran nedir?
close() metodu, throw(GeneratorExit) çağrısıyla jeneratörün mevcut askıya alma noktasına GeneratorExit istisnası gönderir. GeneratorExit, üst düzey Exception'dan ziyade BaseException'dan miras alır; bu, onu yanlış bir biçimde yakalayabilecek genel except Exception yöneticilerinden kaçındığı içindir. Eğer jeneratör GeneratorExit'i yakalar ve onu yeniden yükseltirse ya da normal bir şekilde çıkarsa, close() sessizce döner; ancak, eğer jeneratör GeneratorExit'e karşılık bir değer göndermeye çalışırsa, CPython yeni değerler üretmemesi gereken bir kapanan jeneratör nedeniyle RuntimeError yükseltir. Bu mekanizma, zorunlu kapatma sırasında bile, jeneratör gövdesindeki finally bloklarının ve bağlam yöneticilerinin çalıştığını garanti eder.
yield from'un iç içe geçmiş jeneratörler arasında gönderilen değerleri şeffaf bir şekilde nasıl işlediği ve bunun send() ile döngü kullanarak manuel delegasyondan nasıl farklılaştığı nedir?
yield from sözdizimi yalnızca yinelemeyi değil, tüm jeneratör protokolünü bir alt jeneratöre devreder. Dış jeneratör yield from subgen() ifadesini yürüttüğünde, CPython çağrıcının send(value) işlevini doğrudan alt jeneratöre göndermeye dönüştürür; bu noktada, StopIteration yükselene kadar (değeri, yield from ifadesinin sonucu haline gelir). Bu, alt jeneratöre yönlendirdikleri değerleri içeren dış jeneratörde gerçekleştirilen for x in subgen(): yield x şeklinde bir döngüde manuel delegasyondan farklıdır. yield from yapısı esasen çağrı yığınını düzleştirir, boilerplate yönlendirme kodunu ortadan kaldırarak, uygun istisna propagasyonu ve kapatma semantiklerini korurken, rastgele derinlikte jeneratör iç içe geçişleri ile iki yönlü veri akışı sağlar.