python jeneratör "gönderme" fonksiyon amacı?


165

Birisi bana Python üreteci işleviyle ilişkili "gönder" işlevinin neden var olduğunu gösteren bir örnek verebilir mi? Verim fonksiyonunu tamamen anlıyorum. Ancak, gönderme işlevi benim için kafa karıştırıcı. Bu yönteme ilişkin belgeler kıvrıktır:

generator.send(value)

Yürütmeyi sürdürür ve jeneratör işlevine bir değer “gönderir”. Değer bağımsız değişkeni, geçerli verim ifadesinin sonucu olur. Send () yöntemi, jeneratör tarafından verilen bir sonraki değeri döndürür veya jeneratör başka bir değer vermeden çıkarsa StopIteration öğesini yükseltir.

Bu ne anlama geliyor? Değerin işleve girdi olduğunu düşündüm. "Send () yöntemi jeneratörün vermiş olduğu bir sonraki değeri döndürür" ifadesi aynı zamanda verim fonksiyonunun tam amacı gibi görünmektedir; verim, jeneratör tarafından verilen bir sonraki değeri döndürür ...

Birisi bana verimi kullanan bir şeyi gerçekleştiren bir üretime örnek verebilir mi?




2
Resmi belgeden alıntılanan ve sorudaki atıfın yapıldığı " send()Jeneratörü başlatmak için çağrıldığında None, argüman olarak çağrılmalıdır , çünkü değeri alabilecek bir verim ifadesi yoktur." eksik.
Rick

Yanıtlar:


147

Yeni çıkan bir jeneratöre değerler göndermek için kullanılır. İşte yapay (kullanışlı olmayan) açıklayıcı bir örnek:

>>> def double_inputs():
...     while True:
...         x = yield
...         yield x * 2
...
>>> gen = double_inputs()
>>> next(gen)       # run up to the first yield
>>> gen.send(10)    # goes into 'x' variable
20
>>> next(gen)       # run up to the next yield
>>> gen.send(6)     # goes into 'x' again
12
>>> next(gen)       # run up to the next yield
>>> gen.send(94.3)  # goes into 'x' again
188.5999999999999

Bunu sadece ile yapamazsın yield.

Neden faydalı olduğuna gelince, gördüğüm en iyi kullanım durumlarından biri Twisted's @defer.inlineCallbacks. Temelde böyle bir işlev yazmanıza izin verir:

@defer.inlineCallbacks
def doStuff():
    result = yield takesTwoSeconds()
    nextResult = yield takesTenSeconds(result * 10)
    defer.returnValue(nextResult / 10)

Olan şey, bir değerin daha sonra hesaplanacağını vaat eden bir değer olan takesTwoSeconds()a döndürür Deferred. Twisted hesaplamayı başka bir iş parçacığında çalıştırabilir. Hesaplama tamamlandığında, bunu ertelenmiş hale getirir ve daha sonra değer doStuff()işleve geri gönderilir . Böylece, doStuff()her türlü hesaplama ve geri arama vb. Yapabilmesi dışında, az çok normal bir prosedür fonksiyonu gibi görünebilir. Bu işlevsellikten önceki alternatif şunun gibi bir şey yapmak olacaktır:

def doStuff():
    returnDeferred = defer.Deferred()
    def gotNextResult(nextResult):
        returnDeferred.callback(nextResult / 10)
    def gotResult(result):
        takesTenSeconds(result * 10).addCallback(gotNextResult)
    takesTwoSeconds().addCallback(gotResult)
    return returnDeferred

Çok daha kıvrımlı ve hantal.


2
Bunun amacının ne olduğunu açıklayabilir misiniz? Bu neden double_inputs (başlangıç ​​numarası) ve verim ile yeniden oluşturulamıyor?
Tommy

@Tommy: oh, sahip olduğunuz değerlerin öncekiyle hiçbir ilgisi yok. örneği değiştireyim
Claudiu

neden bunu girdisini ikiye katlayan basit bir fonksiyon üzerinde kullanasın ki?
Tommy

4
@Tommy: Yapmazdın. İlk örnek sadece ne yaptığını açıklamaktır. İkinci örnek, gerçekten kullanışlı bir kullanım örneğidir.
Claudiu

1
@Tommy: Gerçekten bu sunuyu kontrol etmek ve her şeyi incelemek istiyorsanız söyleyebilirim . Kısa bir cevap yeterli olmayacaktır çünkü o zaman sadece "Ama bunu böyle yapamaz mıyım?" vb.
Claudiu

96

Bu işlev ortak programlar yazmak içindir

def coroutine():
    for i in range(1, 10):
        print("From generator {}".format((yield i)))
c = coroutine()
c.send(None)
try:
    while True:
        print("From user {}".format(c.send(1)))
except StopIteration: pass

baskılar

From generator 1
From user 2
From generator 1
From user 3
From generator 1
From user 4
...

Kontrolün nasıl ileri geri aktarıldığını görüyor musunuz? Bunlar coroutines. Asenkron IO ve benzeri her türlü havalı şey için kullanılabilirler.

Bunu şöyle düşünün, bir jeneratör ve gönderme yok, tek yönlü bir sokak

==========       yield      ========
Generator |   ------------> | User |
==========                  ========

Ancak gönderme ile, iki yönlü bir sokak haline gelir

==========       yield       ========
Generator |   ------------>  | User |
==========    <------------  ========
                  send

Bu , anında jeneratörlerin davranışını özelleştiren ve kullanıcıya yanıt veren jeneratörün kapısını açar .


3
ancak bir jeneratör fonksiyonu parametre alabilir. "Gönder" jeneratöre parametre göndermenin ötesine nasıl geçer?
Tommy

13
@Tommy Çünkü bir jeneratörü çalışırken parametreleri değiştiremezsiniz. Ona parametreler veriyorsunuz, çalışıyor, bitti. Gönderme ile, bunun biraz çalışır parametreleri vermek, bunu bir değer göndermek ve tekrar farklı bir şey yapar
Daniel Gratzer

2
@Tommy Bu, jeneratörü yeniden başlatacak, bu da çok fazla iş yapmanıza neden olacak
Daniel Gratzer

5
Her şeyden önce Hiçbiri gönderme amacını açıklar mısınız?
Shubham Aggarwal

2
@ShubhamAggarwal Jeneratörü 'başlatmak' için yapılır. Bu sadece yapılması gereken bir şey. Üreticiyi ilk aradığınızda henüz send()anahtar kelimeye ulaşmadığı için bunu düşündüğünüzde bir anlam ifade ediyor yield.
Michael

50

Bu birine yardımcı olabilir. İşte gönderme işlevinden etkilenmeyen bir jeneratör. Örneklemede number parametresini alır ve göndermekten etkilenmez:

>>> def double_number(number):
...     while True:
...         number *=2 
...         yield number
... 
>>> c = double_number(4)
>>> c.send(None)
8
>>> c.next()
16
>>> c.next()
32
>>> c.send(8)
64
>>> c.send(8)
128
>>> c.send(8)
256

Şimdi, send'i kullanarak aynı türde bir işlevi nasıl yapacağınız aşağıda verilmiştir, böylece her yinelemede sayının değerini değiştirebilirsiniz:

def double_number(number):
    while True:
        number *= 2
        number = yield number

Sayı için yeni bir değer göndermenin sonucu değiştirdiğini görebileceğiniz gibi, işte böyle görünüyor:

>>> def double_number(number):
...     while True:
...         number *= 2
...         number = yield number
...
>>> c = double_number(4)
>>> 
>>> c.send(None)
8
>>> c.send(5) #10
10
>>> c.send(1500) #3000
3000
>>> c.send(3) #6
6

Bunu bir for döngüsü içine şu şekilde de koyabilirsiniz:

for x in range(10):
    n = c.send(n)
    print n

Daha fazla yardım için bu harika eğiticiye göz atın .


12
Send () işlevinden etkilenmeyen bir işlev ile bu işlev arasındaki karşılaştırma gerçekten yardımcı oldu. Teşekkürler!
Manas Bajaj

Bu, amacının nasıl açıklayıcı bir örneği olabilir send? Bir basit lambda x: x * 2, aynı şeyi çok daha az kıvrımlı bir şekilde yapar.
Kullanıcı209974

Send kullanıyor mu? Git ve cevabını ekle.
radtek

17

Jeneratör kullanmak için bazı durumlar ve send()

İzin verilen jeneratörler send():

  • infazın iç durumunu hatırlamak
    • hangi adımdayız
    • verilerimizin mevcut durumu nedir
  • değer dizisi döndürme
  • giriş dizisi alma

İşte bazı kullanım durumları:

Bir tarifi takip etme girişimi izlendi

Önceden tanımlanmış bir dizi girdiyi bekleyen bir tarif edelim.

Biz olabilir:

  • watched_attempttariften bir örnek oluştur
  • bazı girdiler almasına izin ver
  • her bir girişte şu anda potada ne olduğu hakkında bilgi döndürür
  • her giriş kontrolünde, girişin beklenen giriş olduğunu (ve eğer değilse)

    def recipe():
        pot = []
        action = yield pot
        assert action == ("add", "water")
        pot.append(action[1])
    
        action = yield pot
        assert action == ("add", "salt")
        pot.append(action[1])
    
        action = yield pot
        assert action == ("boil", "water")
    
        action = yield pot
        assert action == ("add", "pasta")
        pot.append(action[1])
    
        action = yield pot
        assert action == ("decant", "water")
        pot.remove("water")
    
        action = yield pot
        assert action == ("serve")
        pot = []
        yield pot

Kullanmak için önce watched_attemptörneği oluşturun:

>>> watched_attempt = recipe()                                                                         
>>> watched_attempt.next()                                                                                     
[]                                                                                                     

.next()Jeneratörün yürütülmesini başlatmak için çağrı gereklidir.

Döndürülen değer gösteriyor, potumuz şu anda boş.

Şimdi tarifin beklediğini izleyerek birkaç işlem yapın:

>>> watched_attempt.send(("add", "water"))                                                                     
['water']                                                                                              
>>> watched_attempt.send(("add", "salt"))                                                                      
['water', 'salt']                                                                                      
>>> watched_attempt.send(("boil", "water"))                                                                    
['water', 'salt']                                                                                      
>>> watched_attempt.send(("add", "pasta"))                                                                     
['water', 'salt', 'pasta']                                                                             
>>> watched_attempt.send(("decant", "water"))                                                                  
['salt', 'pasta']                                                                                      
>>> watched_attempt.send(("serve"))                                                                            
[] 

Gördüğümüz gibi, pot sonunda boş.

Eğer tarif takip edilmezse, başarısız olur (bir şey pişirmek için izlenen denemenin sonucu istenen ne olabilir - sadece talimatlar verildiğinde yeterince dikkat etmediğimizi öğrenmek.

>>> watched_attempt = running.recipe()                                                                         
>>> watched_attempt.next()                                                                                     
[]                                                                                                     
>>> watched_attempt.send(("add", "water"))                                                                     
['water']                                                                                              
>>> watched_attempt.send(("add", "pasta")) 

---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-21-facdf014fe8e> in <module>()
----> 1 watched_attempt.send(("add", "pasta"))

/home/javl/sandbox/stack/send/running.py in recipe()
     29
     30     action = yield pot
---> 31     assert action == ("add", "salt")
     32     pot.append(action[1])
     33

AssertionError:

Dikkat edin:

  • beklenen adımların doğrusal dizisi var
  • adımlar farklı olabilir (bazıları çıkarılıyor, bazıları pota ekleniyor)
  • tüm bunları bir işlev / jeneratör ile yapmayı başarırız - karmaşık sınıf veya benzer dikmeler kullanmaya gerek yoktur.

Koşu toplamları

Jeneratörü, kendisine gönderilen toplam değerlerin çalışmasını takip etmek için kullanabiliriz.

Bir sayı eklediğimizde, girdi sayısı ve toplam toplam döndürülür (önceki girdinin ona gönderildiği an için geçerlidir).

from collections import namedtuple

RunningTotal = namedtuple("RunningTotal", ["n", "total"])


def runningtotals(n=0, total=0):
    while True:
        delta = yield RunningTotal(n, total)
        if delta:
            n += 1
            total += delta


if __name__ == "__main__":
    nums = [9, 8, None, 3, 4, 2, 1]

    bookeeper = runningtotals()
    print bookeeper.next()
    for num in nums:
        print num, bookeeper.send(num)

Çıktı şöyle görünecektir:

RunningTotal(n=0, total=0)
9 RunningTotal(n=1, total=9)
8 RunningTotal(n=2, total=17)
None RunningTotal(n=2, total=17)
3 RunningTotal(n=3, total=20)
4 RunningTotal(n=4, total=24)
2 RunningTotal(n=5, total=26)
1 RunningTotal(n=6, total=27)

3
Ben senin örnek çalıştırmak ve python 3 watched_attempt.next () sonraki (watched_attempt) ile değiştirilmesi gibi görünüyor.
thanos.a

15

send()Yöntem kontrolleri verim ifadesinin solunda değeri ne olacağı.

Verimin nasıl değiştiğini ve hangi değeri tuttuğunu anlamak için, önce sipariş python kodunun değerlendirildiği siparişi hızla yenileyelim.

Bölüm 6.15 Değerlendirme sırası

Python ifadeleri soldan sağa doğru değerlendirir. Bir ödevi değerlendirirken, sağ tarafın sol taraftan önce değerlendirildiğine dikkat edin.

Bu yüzden a = bönce sağ taraftaki bir ifade değerlendirilir.

Aşağıdaki a[p('left')] = p('right')şekilde sağ tarafın ilk değerlendirildiğini göstermektedir.

>>> def p(side):
...     print(side)
...     return 0
... 
>>> a[p('left')] = p('right')
right
left
>>> 
>>> 
>>> [p('left'), p('right')]
left
right
[0, 0]

Verim ne yapar ?, verim, işlevin yürütülmesini askıya alır ve arayana geri döner ve askıya alınmadan önce kaldığı yerden devam eder.

İcra tam olarak nerede askıya alınır? Bunu zaten tahmin etmiş olabilirsiniz ... yürütme, verim ifadesinin sağ ve sol tarafı arasında askıya alınır. Böylece new_val = yield old_valyürütme =işarette durdurulur ve sağdaki değer (askıya alınmadan önce ve aynı zamanda arayana döndürülen değerdir), soldaki değerden (devam ettirildikten sonra atanan değer) farklı bir şey olabilir çalıştırma).

yield biri sağda diğeri solda olmak üzere 2 değer verir.

Verim ifadesinin sol tarafındaki değeri nasıl kontrol edersiniz? ile .send()yöntem.

6.2.9. Getiri ifadeleri

Yeniden başladıktan sonra verim ifadesinin değeri, yürütmeyi sürdüren yönteme bağlıdır. Eğer __next__()(tipik için ya a ya da üzerinden kullanılan next()yerleşiği) daha sonra sonuç yok olduğunu. Aksi takdirde, send()kullanılırsa, sonuç bu yönteme iletilen değer olacaktır.



6

"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:

  1. Varsayalım ki gjeneratör. g.sendborunun sağ ucundan sola bir değer iter.
  2. gDuraklamalar bağlamında yürütme , jeneratör işlevinin gövdesinin çalışmasına izin verir.
  3. 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.
  4. Yürütme, sonraki fonksiyon satırına yieldulaşılana kadar jeneratör fonksiyonunun gövdesi içinde devam eder .
  5. yieldborunun sol ucundan sağa doğru bir değer iter g.send. In x = yield y, yborudan sağa itilir.
  6. Jeneratör fonksiyonunun gövdesi içindeki yürütme, dış kapsamın kaldığı yerden devam etmesine izin vererek duraklar.
  7. g.send değeri devam ettirir ve çeker ve kullanıcıya döndürür.
  8. 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:

  1. right_end.push(None) # the first half of g.send; sending None is what starts a generator
  2. right_end.pause()
  3. left_end.start()
  4. initial_value = left_end.pull()
  5. if initial_value is not None: raise TypeError("can't send non-None value to a just-started generator")
  6. left_end.do_stuff()
  7. left_end.push(y) # the first half of yield
  8. left_end.pause()
  9. right_end.resume()
  10. value1 = right_end.pull() # the second half of g.send
  11. right_end.do_stuff()
  12. right_end.push(value2) # the first half of g.send (again, but with a different value)
  13. right_end.pause()
  14. left_end.resume()
  15. x = left_end.pull() # the second half of yield
  16. 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:

  1. Uygulamayı iç içe geçmiş bir işleve taşıdık.
  2. 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.
  3. 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.
  4. Yuvalanmış işlev içinde, ifade x = yield yiki satırla değiştirildi: left_end.push(y)ve x = left_end.pull().
  5. Ö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.


"G = f (1) 'deki 1 argümanı normal olarak yakalandı ve f'nin gövdesi içinde y'ye atandı, ancak while True henüz başlamadı." Neden olmasın? Python neden bu kodla karşılaşana kadar bu kodu çalıştırmayı denemiyor yield?
Josh

@Josh İmleç, ilk çağrı yapılana kadar ilerlemez send; send(None)imleci ilk yieldifadeye götürmek için bir çağrı alır ve ancak sonraki sendçağrılar aslında "gerçek" bir değer gönderir yield.
BallpointBen

Teşekkürler - Bu ilginç, bu yüzden yorumlayıcı fonksiyonun bir noktada f olacağını bilir yieldve böylece arayandan bir tane alıncaya kadar bekler sendmi? Normal bir fonksiyonla tercüman hemen çalışmaya başlayacaktır f, değil mi? Sonuçta, Python'da herhangi bir AOT derlemesi yoktur. Durumun bu olduğundan emin misin? (ne söylediğini sorgulamadan, gerçekten burada yazdıklarına şaşkınım). Python'un işlevin geri kalanını yürütmeye başlamadan önce beklemesi gerektiğini nasıl bildikleri hakkında daha fazla bilgiyi nerede bulabilirim?
Josh

@Josh Bu zihinsel modeli, farklı oyuncak üreticilerinin nasıl çalıştığını gözlemleyerek, Python'un içlerini anlamadan inşa ettim. Ancak, ilk olması send(None)verimleri uygun bir değer (örneğin, 1) olmadan göndermek Nonejeneratörü ilk çağrı düşündürmektedir sendözel bir durumdur. Bu tasarım zor bir arayüz; birincisinin sendrasgele bir değer göndermesine izin verirseniz , verilen değerlerin ve gönderilen değerlerin sırası, o andaki değere göre birer birer kapalı olur.
BallpointBen

Teşekkürler BallpointBen. Çok ilginç, neden böyle olduğunu görmek için burada bir soru bıraktım .
Josh

2

Bunlar da beni şaşırttı. İşte sinyalleri dönüşümlü olarak veren ve kabul eden bir jeneratör kurmaya çalışırken yaptığım bir örnek (verim, kabul, verim, kabul) ...

def echo_sound():

    thing_to_say = '<Sound of wind on cliffs>'
    while True:
        thing_to_say = (yield thing_to_say)
        thing_to_say = '...'.join([thing_to_say]+[thing_to_say[-6:]]*2)
        yield None  # This is the return value of send.

gen = echo_sound()

print 'You are lost in the wilderness, calling for help.'

print '------'
in_message = gen.next()
print 'You hear: "{}"'.format(in_message)
out_message = 'Hello!'
print 'You yell "{}"'.format(out_message)
gen.send(out_message)

print '------'
in_message = gen.next()
print 'You hear: "{}"'.format(in_message)
out_message = 'Is anybody out there?'
print 'You yell "{}"'.format(out_message)
gen.send(out_message)

print '------'
in_message = gen.next()
print 'You hear: "{}"'.format(in_message)
out_message = 'Help!'
print 'You yell "{}"'.format(out_message)
gen.send(out_message)

Çıktı:

You are lost in the wilderness, calling for help.
------
You hear: "<Sound of wind on cliffs>"
You yell "Hello!"
------
You hear: "Hello!...Hello!...Hello!"
You yell "Is anybody out there?"
------
You hear: "Is anybody out there?...there?...there?"
You yell "Help!"
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.