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?
send()
dipanggil untuk menghidupkan generator, itu harus disebut denganNone
argumen, karena tidak ada ekspresi hasil yang dapat menerima nilai.", Yang dikutip dari dokumen resmi dan yang kutipan dalam pertanyaannya adalah hilang.Jawaban:
Ini digunakan untuk mengirim nilai ke generator yang baru saja dihasilkan. Berikut ini adalah contoh penjelasan tiruan (tidak berguna):
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:Apa yang terjadi adalah yang
takesTwoSeconds()
mengembalikan aDeferred
, 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 kedoStuff()
fungsi. Dengan demikiandoStuff()
dapat berakhir lebih mirip fungsi prosedural normal, kecuali dapat melakukan segala macam perhitungan & panggilan balik dll. Alternatif sebelum fungsi ini adalah melakukan sesuatu seperti:Ini jauh lebih berbelit-belit dan berat.
sumber
Fungsi ini untuk menulis coroutine
cetakan
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
Tetapi dengan mengirim, itu menjadi jalan dua arah
Yang membuka pintu bagi pengguna menyesuaikan perilaku generator dengan cepat dan generator merespons pengguna.
sumber
send()
generator belum mencapai kata kunciyield
.Ini dapat membantu seseorang. Berikut adalah generator yang tidak terpengaruh oleh fungsi kirim. Dibutuhkan parameter angka pada instantiation dan tidak terpengaruh oleh kirim:
Sekarang di sini adalah bagaimana Anda akan melakukan jenis fungsi yang sama menggunakan kirim, sehingga pada setiap iterasi Anda dapat mengubah nilai angka:
Berikut ini tampilannya, karena Anda dapat melihat pengiriman nilai baru untuk nomor mengubah hasilnya:
Anda juga bisa meletakkan ini dalam for for loop seperti itu:
Untuk bantuan lebih lanjut, lihat tutorial hebat ini .
sumber
send
? Sederhanalambda x: x * 2
melakukan hal yang sama dengan cara yang jauh lebih berbelit-belit.Beberapa menggunakan case untuk menggunakan generator dan
send()
Generator dengan
send()
memungkinkan: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:
watched_attempt
instance dari resepdengan setiap cek input, bahwa input adalah yang diharapkan (dan gagal jika tidak)
Untuk menggunakannya, pertama buat
watched_attempt
instance: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:
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.
Perhatikan itu:
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).
Outputnya akan terlihat seperti:
sumber
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
Jadi ekspresi
a = b
sisi kanan dievaluasi terlebih dahulu.Sebagai berikut menunjukkan bahwa
a[p('left')] = p('right')
sisi kanan dievaluasi terlebih dahulu.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_val
eksekusi 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
sumber
The
send
metode 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.
sumber
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
yield
kata 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
yield
kata kunci, sedangkan ujung kanan adalah generator dan dapat diakses melaluisend
fungsi generator . Sebagai antarmuka tunggal ke masing-masing ujung pipa,yield
dansend
melakukan tugas ganda: mereka masing-masing mendorong dan menarik nilai ke / dari ujung pipa,yield
mendorong ke kanan dan menarik ke kiri sementarasend
melakukan sebaliknya. Tugas ganda ini adalah inti dari kebingungan seputar semantik pernyataan sepertix = yield y
. Memecahyield
- mecahsend
menjadi dua langkah push / pull yang eksplisit akan membuat semantiknya lebih jelas:g
generator.g.send
mendorong nilai ke kiri melalui ujung kanan pipa.g
jeda, memungkinkan tubuh fungsi generator dijalankan.g.send
ditarik ke kiri olehyield
dan diterima di ujung kiri pipa. Inx = yield y
,x
ditugaskan ke nilai yang ditarik.yield
tercapai.yield
mendorong nilai ke kanan melalui ujung kiri pipa, kembali keg.send
. Dix = yield y
,y
didorong ke kanan melalui pipa.g.send
resume dan menarik nilai dan mengembalikannya kepada pengguna.g.send
selanjutnya dipanggil, kembali ke Langkah 1.Meskipun bersifat siklus, prosedur ini memang memiliki permulaan: kapan
g.send(None)
- yang merupakannext(g)
kependekan dari - pertama kali disebut (adalah ilegal untuk melewatkan sesuatu selain dari panggilanNone
pertamasend
). Dan itu mungkin memiliki akhir: ketika tidak ada lagiyield
pernyataan yang harus dicapai dalam tubuh fungsi generator.Apakah Anda melihat apa yang membuat
yield
pernyataan (atau lebih tepatnya, generator) begitu istimewa? Berbeda denganreturn
kata kunci yang sangat sedikit ,yield
mampu 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 memilikireturn
kata kunci juga.) Ketika sebuahyield
pernyataan ditemui, fungsi generator hanya berhenti, dan kemudian mengambil kembali tepat di tempat itu meninggalkan off saat dikirim nilai lain. Dansend
hanya 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,
yield
dansend
merupakan dua sisi dari pipakoin yangsama :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
Transformasi kuncinya adalah bahwa kami telah membagi
x = yield y
danvalue1 = g.send(value2)
masing - masing menjadi dua pernyataan:left_end.push(y)
danx = left_end.pull()
; danvalue1 = right_end.pull()
danright_end.push(value2)
. Ada dua kasus khusus dariyield
kata kunci:x = yield
danyield y
. Ini adalah gula sintaktis, masing-masing, untukx = yield None
dan_ = 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 dengang.send(None)
. Dengan pemikiran ini, kita hanya dapat fokus pada carasend
kerjanya dan hanya berbicara tentang memajukan generatorsend
.Misalkan kita punya
Sekarang, definisi
f
kasar kira - kira untuk fungsi biasa (non-generator) berikut:Berikut ini telah terjadi dalam transformasi ini
f
:left_end
akan diakses oleh fungsi bersarang dan yangright_end
akan dikembalikan dan diakses oleh lingkup luar -right_end
adalah apa yang kita kenal sebagai objek generator.left_end.pull()
adalahNone
, mengkonsumsi nilai mendorong dalam proses.x = yield y
telah diganti oleh dua baris:left_end.push(y)
danx = left_end.pull()
.send
fungsi untukright_end
, yang merupakan lawan dari dua baris yang kami ganti denganx = yield y
pernyataan pada langkah sebelumnya.Dalam dunia fantasi ini di mana fungsi dapat berlanjut setelah kembali,
g
ditugaskanright_end
dan kemudianimpl()
dipanggil. Jadi dalam contoh kita di atas, jika kita mengikuti eksekusi baris demi baris, apa yang akan terjadi kira-kira sebagai berikut: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
send
digunakan.Dengan menggunakan aturan penentuan yang sama ini, mari kita lihat dua kasus khusus:
Sebagian besar mereka desugar dengan cara yang sama seperti
f
, satu-satunya perbedaan adalah bagaimanayield
pernyataan diubah:Dalam yang pertama, nilai yang diteruskan ke
f1
didorong (dihasilkan) pada awalnya, dan kemudian semua nilai yang ditarik (dikirim) didorong (dihasilkan) segera kembali. Dalam yang kedua,x
tidak memiliki nilai (belum) ketika pertama kali datangpush
, jadiUnboundLocalError
dinaikkan.sumber
yield
?send
; diperlukan satu panggilansend(None)
untuk memindahkan kursor keyield
pernyataan pertama , dan hanya kemudiansend
panggilan selanjutnya benar-benar mengirim nilai "nyata" keyield
.f
akanyield
di beberapa titik, dan dengan demikian menunggu sampai mendapatsend
dari pemanggil? Dengan fungsi normal, penerjemah akan langsung mulai mengeksekusif
, 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?send(None)
menghasilkan nilai yang sesuai (misalnya,1
) tanpa mengirimNone
ke generator menunjukkan bahwa panggilan pertama kesend
adalah kasus khusus. Ini adalah antarmuka yang rumit untuk desain; jika Anda membiarkan yang pertamasend
mengirim nilai arbitrer, maka urutan nilai yang dihasilkan dan nilai yang dikirim akan dimatikan satu dibandingkan dengan apa yang saat ini.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) ...
Outputnya adalah:
sumber