"Verim" kelimesinin iki anlamı vardır: bir şey üretmek (örneğin mısır vermek) ve birisinin / başka bir şeyin devam etmesine izin vermek (örneğin, yayalara giden arabalar) için durmak. Her iki tanım da Python'un yieldanahtar kelimesi için geçerlidir ; Jeneratör fonksiyonlarını özel yapan şey, normal fonksiyonlardan farklı olarak, bir jeneratör fonksiyonunu sonlandırmak yerine sadece duraklatmak, değerlerin arayana "geri" döndürülebilmesidir.
Bir jeneratörü, "sol" ucu ve "sağ" ucu olan çift yönlü bir borunun bir ucu olarak hayal etmek en kolay yoldur; bu boru, jeneratörün kendisi ile jeneratör fonksiyonunun gövdesi arasında değerlerin gönderildiği ortamdır. Borunun her bir ucunun iki işlemi vardır: bu push, bir değer gönderir ve borunun diğer ucu değeri çekinceye kadar bloke eder ve hiçbir şey döndürmez; vepull diğer ucu bir değer itene kadar bloke olan ve itilen değeri döndüren blok. Çalışma zamanında yürütme, borunun her iki tarafındaki bağlamlar arasında ileri geri sıçrar - her bir taraf diğer tarafa bir değer gönderene kadar çalışır, bu noktada durur, diğer tarafın çalışmasına izin verir ve bir değer bekler diğer taraf durur ve devam eder. Başka bir deyişle, borunun her bir ucu bir değer aldığı andan bir değer gönderdiği ana kadar ilerler.
Boru işlevsel simetriktir, ancak - Bu yanıtında tanımlayarak ediyorum teamül - sol uç jeneratör işlevin vücudunun içinde kullanılabilir ve erişilebilir yieldsağ uç iken, anahtar kelime olan jeneratör ve erişilebilir jeneratör sendişlevi. Borunun ilgili uçlarına tekil arabirimler olarak yieldve sendçift görev yaparlar: her ikisi de değerleri borunun uçlarına / uçlarından iter ve çeker, yieldsağa iter ve sola doğru çeker send. Bu çifte görev, gibi ifadelerin anlambilimini çevreleyen karışıklığın temelidir x = yield y. Breaking yieldve sendiki açık itme / çekme adımlara aşağı onların semantik çok daha açık hale getirecek:
- Varsayalım ki
gjeneratör. g.sendborunun sağ ucundan sola bir değer iter.
gDuraklamalar bağlamında yürütme , jeneratör işlevinin gövdesinin çalışmasına izin verir.
g.sendİtilen değer sola doğru çekilir yieldve borunun sol ucundan alınır. In x = yield y, xçekilen değere atanır.
- Yürütme, sonraki fonksiyon satırına
yieldulaşılana kadar jeneratör fonksiyonunun gövdesi içinde devam eder .
yieldborunun sol ucundan sağa doğru bir değer iter g.send. In x = yield y, yborudan sağa itilir.
- Jeneratör fonksiyonunun gövdesi içindeki yürütme, dış kapsamın kaldığı yerden devam etmesine izin vererek duraklar.
g.send değeri devam ettirir ve çeker ve kullanıcıya döndürür.
- Bir
g.sendsonraki çağrıldığında, 1. Adıma geri dönün.
Döngüsel olsa da, bu prosedürün bir başlangıcı vardır: ne zaman g.send(None)- yani next(g)kısa olanı - ilk çağrıldığında ( Noneilk sendçağrıdan başka bir şey iletmek yasadışıdır ). Ve bir sonu olabilir: yieldjeneratör fonksiyonunun gövdesinde ulaşılacak başka ifade olmadığında.
yieldİfadeyi (veya daha doğrusu jeneratörleri) bu kadar özel yapan şeyin ne olduğunu görüyor musunuz ? Measly returnanahtar kelimesinin aksine, içinde yieldbulunduğu işlevi sonlandırmadan, arayan kişiye değerleri iletebilir ve arayandan değerleri alabilir! (Tabii ki, bir işlevi veya bir jeneratörü sonlandırmak isterseniz, returnanahtar kelimeye sahip olmak da kullanışlıdır .) Bir yieldifadeyle karşılaşıldığında, jeneratör işlevi sadece duraklar ve ardından kaldığı yerden geri alır başka bir değer gönderildikten sonra kapalı. Ve sendsadece dışarıdan bir jeneratör fonksiyonunun içi ile iletişim kurmak için arayüzdür.
Gerçekten aşağı kadarıyla biz olarak bu itme / çekme / boru benzetme kırmak istiyorsak, gerçekten ev sürücüler olduğunu aşağıdaki pseudocode ile bitirmek, bir kenara adımlar 1-5, gelen yieldve sendaynı iki yüzüdür sikke borusu:
right_end.push(None) # the first half of g.send; sending None is what starts a generator
right_end.pause()
left_end.start()
initial_value = left_end.pull()
if initial_value is not None: raise TypeError("can't send non-None value to a just-started generator")
left_end.do_stuff()
left_end.push(y) # the first half of yield
left_end.pause()
right_end.resume()
value1 = right_end.pull() # the second half of g.send
right_end.do_stuff()
right_end.push(value2) # the first half of g.send (again, but with a different value)
right_end.pause()
left_end.resume()
x = left_end.pull() # the second half of yield
goto 6
Anahtar dönüşüm biz bölünmüş olması x = yield yve value1 = g.send(value2)iki ifade her: left_end.push(y)ve x = left_end.pull(); ve value1 = right_end.pull()ve right_end.push(value2). yieldAnahtar kelimenin iki özel durumu vardır : x = yieldve yield y. Bunlar için sırasıyla sözdizimsel şeker x = yield Noneve _ = yield y # discarding value.
Borudan değerlerin gönderilme sırasına ilişkin özel ayrıntılar için aşağıya bakın.
Aşağıdakiler, yukarıdakilerin oldukça uzun bir somut modelidir. Birincisi, ilk önce herhangi jeneratör için belirtmek gerekir g, next(g)tam olarak eşdeğerdir g.send(None). Bunu akılda tutarak, sadece nasıl sendçalıştığına odaklanabilir ve sadece jeneratörü ilerletmek hakkında konuşabiliriz send.
Varsayalım
def f(y): # This is the "generator function" referenced above
while True:
x = yield y
y = x
g = f(1)
g.send(None) # yields 1
g.send(2) # yields 2
Şimdi, faşağıdaki sıradan (jeneratör olmayan) fonksiyonun kabaca desugarlarının tanımı :
def f(y):
bidirectional_pipe = BidirectionalPipe()
left_end = bidirectional_pipe.left_end
right_end = bidirectional_pipe.right_end
def impl():
initial_value = left_end.pull()
if initial_value is not None:
raise TypeError(
"can't send non-None value to a just-started generator"
)
while True:
left_end.push(y)
x = left_end.pull()
y = x
def send(value):
right_end.push(value)
return right_end.pull()
right_end.send = send
# This isn't real Python; normally, returning exits the function. But
# pretend that it's possible to return a value from a function and then
# continue execution -- this is exactly the problem that generators were
# designed to solve!
return right_end
impl()
Bu dönüşümde şunlar oldu f:
- Uygulamayı iç içe geçmiş bir işleve taşıdık.
left_endYuvalanmış işlevle right_enderişilecek ve dış kapsam tarafından döndürülecek ve erişilecek çift yönlü bir boru oluşturduk - right_endjeneratör nesnesi olarak bildiğimiz şey bu.
- Yuvalanmış fonksiyon içinde yaptığımız ilk şey , süreçte itilmiş bir değer tüketmek olduğunu kontrol
left_end.pull()etmektir None.
- Yuvalanmış işlev içinde, ifade
x = yield yiki satırla değiştirildi: left_end.push(y)ve x = left_end.pull().
- Önceki adımda ifadeyi değiştirdiğimiz iki satırın karşılığı olan
sendişlevi tanımladık .right_endx = yield y
İşlevlerin döndükten sonra da devam edebileceği bu fantezi dünyasında g, atanır right_endve sonra impl()çağrılır. Yukarıdaki örneğimizde, yürütmeyi satır satır takip edecek olsaydık, ne olacağı şu şekildedir:
left_end = bidirectional_pipe.left_end
right_end = bidirectional_pipe.right_end
y = 1 # from g = f(1)
# None pushed by first half of g.send(None)
right_end.push(None)
# The above push blocks, so the outer scope halts and lets `f` run until
# *it* blocks
# Receive the pushed value, None
initial_value = left_end.pull()
if initial_value is not None: # ok, `g` sent None
raise TypeError(
"can't send non-None value to a just-started generator"
)
left_end.push(y)
# The above line blocks, so `f` pauses and g.send picks up where it left off
# y, aka 1, is pulled by right_end and returned by `g.send(None)`
right_end.pull()
# Rinse and repeat
# 2 pushed by first half of g.send(2)
right_end.push(2)
# Once again the above blocks, so g.send (the outer scope) halts and `f` resumes
# Receive the pushed value, 2
x = left_end.pull()
y = x # y == x == 2
left_end.push(y)
# The above line blocks, so `f` pauses and g.send(2) picks up where it left off
# y, aka 2, is pulled by right_end and returned to the outer scope
right_end.pull()
x = left_end.pull()
# blocks until the next call to g.send
Bu, yukarıdaki 16 adımlı sahte kodla tam olarak eşleşir.
Hataların nasıl yayıldığı ve jeneratörün sonuna ulaştığınızda ne olacağı gibi başka ayrıntılar da var (boru kapalı), ancak bu, temel kontrol akışının sendkullanıldığında nasıl çalıştığını netleştirmelidir .
Bu aynı desugaring kurallarını kullanarak iki özel duruma bakalım:
def f1(x):
while True:
x = yield x
def f2(): # No parameter
while True:
x = yield x
Çoğunlukla aynı şekilde desugarlar f, tek farklar ifadelerin nasıl yielddönüştürüldüğüdür:
def f1(x):
# ... set up pipe
def impl():
# ... check that initial sent value is None
while True:
left_end.push(x)
x = left_end.pull()
# ... set up right_end
def f2():
# ... set up pipe
def impl():
# ... check that initial sent value is None
while True:
left_end.push(x)
x = left_end.pull()
# ... set up right_end
İlkinde, aktarılan değer f1başlangıçta itilir (verilir) ve daha sonra çekilen (gönderilen) tüm değerler geri itilir (verilir). İkincisi, xilk kez geldiğinde (henüz) hiçbir değeri yoktur push, bu yüzden bir UnboundLocalErroryükseltilir.