python generator "mengirim" tujuan fungsi?

165

Bisakah seseorang memberi saya contoh mengapa fungsi "kirim" yang terkait dengan fungsi generator Python ada? Saya sepenuhnya memahami fungsi hasil. Namun, fungsi kirim membingungkan bagi saya. Dokumentasi tentang metode ini berbelit-belit:

generator.send(value)

Melanjutkan eksekusi dan "mengirim" nilai ke fungsi generator. Argumen nilai menjadi hasil dari ekspresi hasil saat ini. Metode send () mengembalikan nilai berikutnya yang dihasilkan oleh generator, atau menaikkan StopIteration jika generator keluar tanpa menghasilkan nilai lain.

Apa artinya? Saya pikir nilai adalah input ke fungsi? Ungkapan "Metode kirim () mengembalikan nilai berikutnya yang dihasilkan oleh generator" tampaknya juga merupakan tujuan yang tepat dari fungsi hasil; hasil mengembalikan nilai berikutnya yang dihasilkan oleh generator ...

Bisakah seseorang memberi saya contoh generator yang memanfaatkan pengiriman yang menghasilkan sesuatu yang tidak bisa?

Tommy
sumber
3
duplikat: stackoverflow.com/questions/12637768/…
Bas Swinckels
3
Menambahkan contoh kehidupan nyata lainnya (membaca dari FTP) ketika panggilan balik diubah menjadi generator yang digunakan dari dalam
Jan Vlcinsky
2
Perlu disebutkan bahwa "Ketika send()dipanggil untuk menghidupkan generator, itu harus disebut dengan Noneargumen, karena tidak ada ekspresi hasil yang dapat menerima nilai.", Yang dikutip dari dokumen resmi dan yang kutipan dalam pertanyaannya adalah hilang.
Rick

Jawaban:

147

Ini digunakan untuk mengirim nilai ke generator yang baru saja dihasilkan. Berikut ini adalah contoh penjelasan tiruan (tidak berguna):

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

Anda tidak dapat melakukan ini hanya dengan yield.

Mengenai mengapa ini berguna, salah satu kasus penggunaan terbaik yang pernah saya lihat adalah Twisted @defer.inlineCallbacks. Pada dasarnya itu memungkinkan Anda untuk menulis fungsi seperti ini:

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

Apa yang terjadi adalah yang takesTwoSeconds()mengembalikan a Deferred, yang merupakan nilai yang menjanjikan nilai yang akan dihitung nanti. Twisted dapat menjalankan perhitungan di utas lain. Ketika perhitungan dilakukan, ia meneruskannya ke yang ditangguhkan, dan nilai kemudian dikirim kembali ke doStuff()fungsi. Dengan demikian doStuff()dapat berakhir lebih mirip fungsi prosedural normal, kecuali dapat melakukan segala macam perhitungan & panggilan balik dll. Alternatif sebelum fungsi ini adalah melakukan sesuatu seperti:

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

Ini jauh lebih berbelit-belit dan berat.

Claudiu
sumber
2
Bisakah Anda jelaskan apa tujuannya? Mengapa ini tidak dapat dibuat kembali dengan double_inputs (startingnumber) dan hasil?
Tommy
@Tommy: oh karena nilai yang Anda dapatkan tidak ada hubungannya dengan yang sebelumnya. izinkan saya mengubah contoh
Claudiu
mengapa Anda menggunakan ini daripada fungsi sederhana yang menggandakan inputnya ??
Tommy
4
@Tommy: Kamu tidak akan. Contoh pertama hanya menjelaskan apa yang dilakukannya. Contoh kedua adalah untuk use case yang sebenarnya berguna.
Claudiu
1
@Tommy: Saya akan mengatakan jika Anda benar-benar ingin tahu lihat presentasi ini dan mengerjakan semuanya. Sebuah jawaban singkat tidak akan cukup karena Anda hanya akan berkata "Tapi tidak bisakah saya melakukannya seperti ini?" dll.
Claudiu
96

Fungsi ini untuk menulis coroutine

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

cetakan

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

Lihat bagaimana kontrol dilewatkan bolak-balik? Itu adalah coroutine. Mereka dapat digunakan untuk semua jenis hal-hal keren seperti asynch IO dan sejenisnya.

Pikirkan seperti ini, dengan generator dan tanpa kirim, ini jalan satu arah

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

Tetapi dengan mengirim, itu menjadi jalan dua arah

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

Yang membuka pintu bagi pengguna menyesuaikan perilaku generator dengan cepat dan generator merespons pengguna.

Daniel Gratzer
sumber
3
tetapi fungsi generator dapat mengambil parameter. Bagaimana "Kirim" melampaui mengirim parameter ke generator?
Tommy
13
@Tommy Karena Anda tidak dapat mengubah parameter ke generator saat dijalankan. Anda memberikannya parameter, itu berjalan, selesai. Dengan send, Anda memberikan parameter, itu berjalan sedikit, Anda mengirimkannya nilai dan melakukan sesuatu yang berbeda, ulangi
Daniel Gratzer
2
@Tommy Ini akan menghidupkan ulang generator, yang akan menyebabkan Anda mengulang banyak pekerjaan
Daniel Gratzer
5
Bisakah Anda jelaskan tujuan mengirim Tidak Ada sebelum semuanya?
Shubham Aggarwal
2
@ShubhamAggarwal Ini dilakukan untuk 'memulai' generator. Itu hanya sesuatu yang perlu dilakukan. Masuk akal ketika Anda memikirkannya sejak pertama kali Anda menelepon send()generator belum mencapai kata kunci yield.
Michael
50

Ini dapat membantu seseorang. Berikut adalah generator yang tidak terpengaruh oleh fungsi kirim. Dibutuhkan parameter angka pada instantiation dan tidak terpengaruh oleh kirim:

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

Sekarang di sini adalah bagaimana Anda akan melakukan jenis fungsi yang sama menggunakan kirim, sehingga pada setiap iterasi Anda dapat mengubah nilai angka:

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

Berikut ini tampilannya, karena Anda dapat melihat pengiriman nilai baru untuk nomor mengubah hasilnya:

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

Anda juga bisa meletakkan ini dalam for for loop seperti itu:

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

Untuk bantuan lebih lanjut, lihat tutorial hebat ini .

radtek
sumber
12
Perbandingan antara fungsi yang tidak terpengaruh oleh send () dengan yang tidak, sangat membantu. Terima kasih!
Manas Bajaj
Bagaimana ini bisa menjadi contoh ilustrasi dari tujuan send? Sederhana lambda x: x * 2melakukan hal yang sama dengan cara yang jauh lebih berbelit-belit.
user209974
Apakah ini menggunakan kirim? Pergi dan tambahkan jawaban Anda.
radtek
17

Beberapa menggunakan case untuk menggunakan generator dan send()

Generator dengan send()memungkinkan:

  • mengingat keadaan internal eksekusi
    • apa langkah kita sekarang
    • apa status terkini dari data kami
  • mengembalikan urutan nilai
  • menerima urutan input

Berikut beberapa kasus penggunaan:

Tonton upaya untuk mengikuti resep

Mari kita punya resep, yang mengharapkan serangkaian input yang telah ditentukan dalam urutan tertentu.

Kita boleh:

  • buat watched_attemptinstance dari resep
  • biarkan mendapatkan beberapa input
  • dengan setiap input mengembalikan informasi tentang apa yang saat ini ada di pot
  • dengan setiap cek input, bahwa input adalah yang diharapkan (dan gagal jika tidak)

    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

Untuk menggunakannya, pertama buat watched_attemptinstance:

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

Panggilan ke .next()diperlukan untuk memulai eksekusi generator.

Nilai yang dikembalikan menunjukkan, pot kami saat ini kosong.

Sekarang lakukan beberapa tindakan berikut apa yang diharapkan resep:

>>> 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"))                                                                            
[] 

Seperti yang kita lihat, pot itu akhirnya kosong.

Dalam kasus, seseorang tidak akan mengikuti resep, itu akan gagal (apa yang bisa diinginkan sebagai hasil dari upaya yang diawasi untuk memasak sesuatu - hanya belajar kita tidak membayar cukup perhatian ketika diberi instruksi.

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

Perhatikan itu:

  • ada urutan linear dari langkah-langkah yang diharapkan
  • langkah-langkahnya mungkin berbeda (ada yang menghapus, ada yang menambah pot)
  • kami berhasil melakukan semua itu dengan fungsi / generator - tidak perlu menggunakan kelas kompleks atau struktur yang serupa.

Total berlari

Kami dapat menggunakan generator untuk melacak menjalankan total nilai yang dikirim kepadanya.

Setiap kali kami menambahkan angka, jumlah input dan jumlah total dikembalikan (berlaku untuk saat input sebelumnya dikirim ke dalamnya).

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)

Outputnya akan terlihat seperti:

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)
Jan Vlcinsky
sumber
3
Saya menjalankan contoh Anda dan dalam python 3 tampaknya the watch_attempt.next () harus diganti dengan next (menyaksikan_attempt).
thanos.a
15

The send()metode kontrol apa nilai di sebelah kiri ekspresi yield akan.

Untuk memahami bagaimana hasil berbeda dan nilai apa yang dipegangnya, pertama mari kita segarkan dengan cepat kode urutan python dievaluasi.

Bagian 6.15 Urutan evaluasi

Python mengevaluasi ekspresi dari kiri ke kanan. Perhatikan bahwa saat mengevaluasi suatu tugas, sisi kanan dievaluasi sebelum sisi kiri.

Jadi ekspresi a = bsisi kanan dievaluasi terlebih dahulu.

Sebagai berikut menunjukkan bahwa a[p('left')] = p('right')sisi kanan dievaluasi terlebih dahulu.

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

Apa yang dilakukan yield ?, menghasilkan, menunda eksekusi fungsi dan kembali ke pemanggil, dan melanjutkan eksekusi di tempat yang sama dengan yang ditinggalkannya sebelum ditangguhkan.

Di mana tepatnya eksekusi ditangguhkan? Anda mungkin sudah menebaknya ... eksekusi ditangguhkan antara sisi kanan dan kiri dari ekspresi hasil. Jadi new_val = yield old_valeksekusi terhenti pada =tanda, dan nilai di sebelah kanan (yang sebelum penangguhan, dan juga nilai yang dikembalikan ke penelepon) mungkin sesuatu yang berbeda maka nilai di sebelah kiri (yang merupakan nilai yang ditugaskan setelah melanjutkan eksekusi).

yield menghasilkan 2 nilai, satu ke kanan dan satu lagi ke kiri.

Bagaimana Anda mengontrol nilai di sisi kiri ekspresi hasil? melalui .send()metode.

6.2.9. Ekspresi hasil

Nilai ekspresi hasil setelah melanjutkan tergantung pada metode yang melanjutkan eksekusi. Jika __next__()digunakan (biasanya melalui for atau next()builtin) maka hasilnya adalah None. Jika tidak, jika send()digunakan, maka hasilnya akan menjadi nilai yang diteruskan ke metode itu.

pengguna2059857
sumber
13

The sendmetode alat coroutines .

Jika Anda belum menemukan Coroutine, mereka sulit untuk dikungkung karena mengubah cara program mengalir. Anda dapat membaca tutorial yang bagus untuk lebih jelasnya.

Jochen Ritzel
sumber
6

Kata "hasil" memiliki dua arti: untuk menghasilkan sesuatu (misalnya, untuk menghasilkan jagung), dan untuk menghentikan membiarkan orang / hal lain terus berlanjut (misalnya, mobil yang menghasilkan untuk pejalan kaki). Kedua definisi tersebut berlaku untuk yieldkata kunci Python ; apa yang membuat fungsi generator istimewa adalah bahwa tidak seperti dalam fungsi biasa, nilai-nilai dapat "dikembalikan" ke pemanggil sementara hanya menjeda, tidak mengakhiri, fungsi generator.

Paling mudah membayangkan generator sebagai salah satu ujung pipa dua arah dengan ujung "kiri" dan ujung "kanan"; pipa ini adalah medium di mana nilai dikirim antara generator itu sendiri dan tubuh fungsi generator. Setiap ujung pipa memiliki dua operasi push:, yang mengirimkan nilai dan blok sampai ujung pipa lainnya menarik nilai, dan tidak mengembalikan apa pun; danpull, yang memblokir sampai ujung pipa mendorong nilai, dan mengembalikan nilai yang didorong. Saat runtime, eksekusi memantul bolak-balik antara konteks di kedua sisi pipa - setiap sisi berjalan hingga mengirim nilai ke sisi lain, di mana titik itu berhenti, membiarkan sisi lain berjalan, dan menunggu nilai dalam kembali, di mana sisi lain berhenti dan dilanjutkan. Dengan kata lain, setiap ujung pipa beroperasi dari saat menerima nilai hingga saat mengirimkan nilai.

Pipa fungsional simetris, tetapi - dengan konvensi saya mendefinisikan dalam jawaban ini - ujung kiri hanya tersedia di dalam tubuh fungsi generator dan dapat diakses melalui yieldkata kunci, sedangkan ujung kanan adalah generator dan dapat diakses melalui sendfungsi generator . Sebagai antarmuka tunggal ke masing-masing ujung pipa, yielddan sendmelakukan tugas ganda: mereka masing-masing mendorong dan menarik nilai ke / dari ujung pipa, yieldmendorong ke kanan dan menarik ke kiri sementara sendmelakukan sebaliknya. Tugas ganda ini adalah inti dari kebingungan seputar semantik pernyataan seperti x = yield y. Memecah yield- mecah sendmenjadi dua langkah push / pull yang eksplisit akan membuat semantiknya lebih jelas:

  1. Misalkan ggenerator. g.sendmendorong nilai ke kiri melalui ujung kanan pipa.
  2. Eksekusi dalam konteks gjeda, memungkinkan tubuh fungsi generator dijalankan.
  3. Nilai yang didorong oleh g.sendditarik ke kiri oleh yielddan diterima di ujung kiri pipa. In x = yield y, xditugaskan ke nilai yang ditarik.
  4. Eksekusi berlanjut di dalam tubuh fungsi generator hingga baris berikutnya yang berisi yieldtercapai.
  5. yieldmendorong nilai ke kanan melalui ujung kiri pipa, kembali ke g.send. Di x = yield y, ydidorong ke kanan melalui pipa.
  6. Eksekusi di dalam tubuh fungsi generator berhenti, yang memungkinkan lingkup luar untuk melanjutkan di mana ia tinggalkan.
  7. g.send resume dan menarik nilai dan mengembalikannya kepada pengguna.
  8. Kapan g.sendselanjutnya dipanggil, kembali ke Langkah 1.

Meskipun bersifat siklus, prosedur ini memang memiliki permulaan: kapan g.send(None)- yang merupakan next(g)kependekan dari - pertama kali disebut (adalah ilegal untuk melewatkan sesuatu selain dari panggilan Nonepertama send). Dan itu mungkin memiliki akhir: ketika tidak ada lagi yieldpernyataan yang harus dicapai dalam tubuh fungsi generator.

Apakah Anda melihat apa yang membuat yieldpernyataan (atau lebih tepatnya, generator) begitu istimewa? Berbeda dengan returnkata kunci yang sangat sedikit , yieldmampu memberikan nilai kepada peneleponnya dan menerima nilai dari peneleponnya semuanya tanpa menghentikan fungsi yang dihadapinya! (Tentu saja, jika Anda ingin menghentikan suatu fungsi - atau generator - itu berguna untuk memiliki returnkata kunci juga.) Ketika sebuah yieldpernyataan ditemui, fungsi generator hanya berhenti, dan kemudian mengambil kembali tepat di tempat itu meninggalkan off saat dikirim nilai lain. Dan sendhanya antarmuka untuk berkomunikasi dengan bagian dalam fungsi generator dari luar.

Jika kita benar-benar ingin memecah analogi push / pull / pipe ini sejauh yang kita bisa, kita berakhir dengan pseudocode berikut yang benar-benar mendorong kita pulang, selain dari langkah 1-5, yielddan sendmerupakan dua sisi dari pipa koin yang sama :

  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

Transformasi kuncinya adalah bahwa kami telah membagi x = yield ydan value1 = g.send(value2)masing - masing menjadi dua pernyataan: left_end.push(y)dan x = left_end.pull(); dan value1 = right_end.pull()dan right_end.push(value2). Ada dua kasus khusus dari yieldkata kunci: x = yielddan yield y. Ini adalah gula sintaktis, masing-masing, untuk x = yield Nonedan _ = yield y # discarding value.

Untuk detail spesifik mengenai urutan tepat di mana nilai dikirim melalui pipa, lihat di bawah.


Berikut ini adalah model konkret yang agak panjang di atas. Pertama, harus dicatat bahwa untuk generator apa pun g, next(g)persis sama dengan g.send(None). Dengan pemikiran ini, kita hanya dapat fokus pada cara sendkerjanya dan hanya berbicara tentang memajukan generator send.

Misalkan kita punya

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

Sekarang, definisi fkasar kira - kira untuk fungsi biasa (non-generator) berikut:

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()

Berikut ini telah terjadi dalam transformasi ini f:

  1. Kami telah memindahkan implementasi ke fungsi bersarang.
  2. Kami telah membuat pipa dua arah yang left_endakan diakses oleh fungsi bersarang dan yang right_endakan dikembalikan dan diakses oleh lingkup luar - right_endadalah apa yang kita kenal sebagai objek generator.
  3. Dalam fungsi bersarang, hal pertama yang kita lakukan adalah cek yang left_end.pull()adalah None, mengkonsumsi nilai mendorong dalam proses.
  4. Dalam fungsi bersarang, pernyataan x = yield ytelah diganti oleh dua baris: left_end.push(y)dan x = left_end.pull().
  5. Kami telah mendefinisikan sendfungsi untuk right_end, yang merupakan lawan dari dua baris yang kami ganti dengan x = yield ypernyataan pada langkah sebelumnya.

Dalam dunia fantasi ini di mana fungsi dapat berlanjut setelah kembali, gditugaskan right_enddan kemudian impl()dipanggil. Jadi dalam contoh kita di atas, jika kita mengikuti eksekusi baris demi baris, apa yang akan terjadi kira-kira sebagai berikut:

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

Ini memetakan tepat ke pseudocode 16 langkah di atas.

Ada beberapa detail lainnya, seperti bagaimana kesalahan disebarkan dan apa yang terjadi ketika Anda mencapai ujung generator (pipa ditutup), tetapi ini harus memperjelas bagaimana aliran kontrol dasar bekerja saat senddigunakan.

Dengan menggunakan aturan penentuan yang sama ini, mari kita lihat dua kasus khusus:

def f1(x):
    while True:
        x = yield x

def f2():  # No parameter
    while True:
        x = yield x

Sebagian besar mereka desugar dengan cara yang sama seperti f, satu-satunya perbedaan adalah bagaimana yieldpernyataan diubah:

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

Dalam yang pertama, nilai yang diteruskan ke f1didorong (dihasilkan) pada awalnya, dan kemudian semua nilai yang ditarik (dikirim) didorong (dihasilkan) segera kembali. Dalam yang kedua, xtidak memiliki nilai (belum) ketika pertama kali datang push, jadi UnboundLocalErrordinaikkan.

BallpointBen
sumber
"Argumen 1 dalam g = f (1) telah ditangkap secara normal dan ditugaskan ke y dalam tubuh f, tetapi sementara True belum dimulai." Kenapa tidak? Mengapa Python tidak mencoba menjalankan kode ini sampai bertemu misalnya yield?
Josh
@ Josh Kursor tidak maju sampai panggilan pertama ke send; diperlukan satu panggilan send(None)untuk memindahkan kursor ke yieldpernyataan pertama , dan hanya kemudian sendpanggilan selanjutnya benar-benar mengirim nilai "nyata" ke yield.
BallpointBen
Terima kasih - Itu menarik, jadi penerjemah tahu bahwa fungsinya f akan yield di beberapa titik, dan dengan demikian menunggu sampai mendapat senddari pemanggil? Dengan fungsi normal, penerjemah akan langsung mulai mengeksekusi f, bukan? Lagi pula, tidak ada kompilasi AOT dalam bentuk apa pun di Python. Apakah Anda yakin itu masalahnya? (tidak mempertanyakan apa yang Anda katakan, saya benar-benar hanya bingung dengan apa yang Anda tulis di sini). Di mana saya bisa membaca lebih lanjut tentang bagaimana Python tahu bahwa ia perlu menunggu sebelum mulai menjalankan fungsi lainnya?
Josh
@Josh Saya membangun model mental ini hanya dengan mengamati bagaimana generator mainan yang berbeda bekerja, tanpa pemahaman internal Python. Namun, fakta bahwa inisial send(None)menghasilkan nilai yang sesuai (misalnya, 1) tanpa mengirim Noneke generator menunjukkan bahwa panggilan pertama ke sendadalah kasus khusus. Ini adalah antarmuka yang rumit untuk desain; jika Anda membiarkan yang pertama sendmengirim nilai arbitrer, maka urutan nilai yang dihasilkan dan nilai yang dikirim akan dimatikan satu dibandingkan dengan apa yang saat ini.
BallpointBen
Terima kasih BallpointBen. Sangat menarik, saya meninggalkan pertanyaan di sini untuk melihat mengapa itu terjadi.
Josh
2

Ini juga membingungkan saya. Berikut adalah contoh yang saya buat ketika mencoba mengatur generator yang menghasilkan dan menerima sinyal secara bergantian (menghasilkan, menerima, menghasilkan, menerima) ...

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)

Outputnya adalah:

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!"
Peter
sumber