Önce bir şey yoldan çekelim. yield from g
Eşdeğer olan açıklama , tümüyle ilgili olanlara for v in g: yield v
adalet yapmaya bile başlamıyoryield from
. Çünkü, bununla yüzleşelim, eğer her yield from
şey for
döngüyü genişletirse , o zaman yield from
dile eklemeyi garanti etmez ve bir sürü yeni özelliğin Python 2.x'te uygulanmasını engellemez.
Ne yield from
yapar o arayan ve alt jeneratör arasında şeffaf bir çift yönlü bağlantı kurar :
Bağlantı, yalnızca üretilen unsurları değil, her şeyi de doğru bir şekilde yayması anlamında "şeffaftır" (örn. İstisnalar yayılır).
Bağlantı verileri hem gönderileceği olabilir anlamda "çift yönlü" dır dan ve karşı bir jeneratör.
( TCP'den bahsediyorsak, yield from g
"şimdi istemcimin soketinin geçici olarak bağlantısını kesin ve bu diğer sunucu soketine yeniden bağlayın" anlamına gelebilir. )
BTW, bir jeneratöre veri göndermenin ne anlama geldiğinden bile emin değilseniz, önce her şeyi bırakmanız ve koroutinleri okumalısınız - çok kullanışlıdırlar ( alt rutinlerle kontrast ), ancak ne yazık ki Python'da daha az bilinir. Dave Beazley'nin Coroutines üzerindeki Meraklı Kursu mükemmel bir başlangıç. Hızlı astar için slaytları 24-33 okuyun .
Verimi kullanarak bir jeneratörden veri okuma
def reader():
"""A generator that fakes a read from a file, socket, etc."""
for i in range(4):
yield '<< %s' % i
def reader_wrapper(g):
# Manually iterate over data produced by reader
for v in g:
yield v
wrap = reader_wrapper(reader())
for i in wrap:
print(i)
# Result
<< 0
<< 1
<< 2
<< 3
Manuel olarak tekrarlamak yerine, reader()
sadece yield from
bunu yapabiliriz .
def reader_wrapper(g):
yield from g
Bu işe yarar ve bir satır kod kaldırdık. Ve muhtemelen niyet biraz daha açık (ya da değil). Ama hiçbir şey hayat değiştirmiyor.
Verimi kullanarak bir jeneratöre (koroutin) veri gönderme - Bölüm 1
Şimdi daha ilginç bir şey yapalım. Haydi kendisine writer
gönderilen verileri kabul eden ve bir sokete, fd'ye vb. Yazan bir coututin oluşturalım .
def writer():
"""A coroutine that writes data *sent* to it to fd, socket, etc."""
while True:
w = (yield)
print('>> ', w)
Şimdi soru şudur: Sarıcı işlevi, yazıcıya veri göndermeyi nasıl ele almalıdır, böylece sargıya gönderilen herhangi bir veri saydam olarak şuraya gönderilir writer()
?
def writer_wrapper(coro):
# TBD
pass
w = writer()
wrap = writer_wrapper(w)
wrap.send(None) # "prime" the coroutine
for i in range(4):
wrap.send(i)
# Expected result
>> 0
>> 1
>> 2
>> 3
Sarıcı kendisine gönderilen verileri kabul etmelidir (belli ki) ve ayrıca StopIteration
for döngüsünün ne zaman tükendiğini de ele almalıdır . Açıkçası sadece yapmak for x in coro: yield x
yapmaz. İşte çalışan bir sürüm.
def writer_wrapper(coro):
coro.send(None) # prime the coro
while True:
try:
x = (yield) # Capture the value that's sent
coro.send(x) # and pass it to the writer
except StopIteration:
pass
Veya bunu yapabiliriz.
def writer_wrapper(coro):
yield from coro
Bu, 6 satır kod tasarrufu sağlar, çok daha okunabilir hale getirir ve çalışır. Sihirli!
Jeneratör verimine veri gönderme - Bölüm 2 - İstisna yönetimi
Daha karmaşık hale getirelim. Yazarımızın istisnaları ele alması gerekiyorsa ne olur? Diyelim ki writer
a tutamakları SpamException
ve ***
bir tane ile karşılaşırsa yazdırıyor .
class SpamException(Exception):
pass
def writer():
while True:
try:
w = (yield)
except SpamException:
print('***')
else:
print('>> ', w)
Değişmezsek ne olur writer_wrapper
? Çalışıyor mu? Hadi deneyelim
# writer_wrapper same as above
w = writer()
wrap = writer_wrapper(w)
wrap.send(None) # "prime" the coroutine
for i in [0, 1, 2, 'spam', 4]:
if i == 'spam':
wrap.throw(SpamException)
else:
wrap.send(i)
# Expected Result
>> 0
>> 1
>> 2
***
>> 4
# Actual Result
>> 0
>> 1
>> 2
Traceback (most recent call last):
... redacted ...
File ... in writer_wrapper
x = (yield)
__main__.SpamException
Um, işe yaramıyor çünkü x = (yield)
sadece istisnayı yükseltiyor ve her şey çöküyor. Çalışmasını sağlayalım, ancak istisnaları manuel olarak ele alıp gönderme veya alt jeneratöre atma ( writer
)
def writer_wrapper(coro):
"""Works. Manually catches exceptions and throws them"""
coro.send(None) # prime the coro
while True:
try:
try:
x = (yield)
except Exception as e: # This catches the SpamException
coro.throw(e)
else:
coro.send(x)
except StopIteration:
pass
Bu çalışıyor.
# Result
>> 0
>> 1
>> 2
***
>> 4
Ama bu da öyle!
def writer_wrapper(coro):
yield from coro
yield from
Şeffaf kolları değerleri gönderme veya alt jeneratörüne değerlerini atma.
Bu yine de tüm köşe vakalarını kapsamıyor. Dış jeneratör kapatılırsa ne olur? Alt jeneratör bir değer döndürdüğünde (evet, Python 3.3+, jeneratörler değer döndürebilir), dönüş değeri nasıl yayılmalıdır? Bu yield from
şeffaf her durumu ele gerçekten etkileyici .yield from
sihirli bir şekilde çalışır ve tüm bu vakaları ele alır.
Ben şahsen yield from
kötü bir anahtar kelime seçim olduğunu hissediyorum çünkü iki yönlü doğa belirgin yapmaz . delegate
Dile yeni bir anahtar kelime eklemek mevcut anahtar kelimeleri birleştirmekten çok daha zor olduğu için önerilen başka anahtar kelimeler vardı (beğenildi ancak reddedildi).
Özetle, düşünmek en iyisi yield from
bir şekildetransparent two way channel
arayan ile alt jeneratör arasında .
Referanslar:
- PEP 380 - Bir alt jeneratöre devretme sözdizimi (Ewing) [v3.3, 2009-02-13]
- PEP 342 - Geliştirilmiş Jeneratörler (GvR, Eby) ile Eşsiz Çalışmalar [v2.5, 2005-05-10]