Apakah memiliki fasilitas bahasa generator seperti yield
ide yang bagus?
Saya ingin menjawab ini dari perspektif Python dengan ya tegas , itu ide bagus .
Saya akan mulai dengan membahas beberapa pertanyaan dan asumsi dalam pertanyaan Anda terlebih dahulu, kemudian menunjukkan kegunaan generator dan kegunaannya yang tidak masuk akal di Python nanti.
Dengan fungsi non-generator biasa Anda dapat memanggilnya dan jika diberi input yang sama, itu akan mengembalikan output yang sama. Dengan hasil, ia mengembalikan output yang berbeda, berdasarkan kondisi internal.
Ini salah. Metode pada objek dapat dianggap sebagai fungsi itu sendiri, dengan keadaan internal mereka sendiri. Dalam Python, karena semuanya adalah objek, Anda sebenarnya bisa mendapatkan metode dari objek, dan meneruskan metode itu (yang terikat pada objek asalnya, jadi ia mengingat kondisinya).
Contoh lain termasuk fungsi acak sengaja serta metode input seperti jaringan, sistem file, dan terminal.
Bagaimana fungsi seperti ini cocok dengan paradigma bahasa?
Jika paradigma bahasa mendukung hal-hal seperti fungsi kelas satu, dan generator mendukung fitur bahasa lain seperti protokol Iterable, maka mereka cocok dengan mulus.
Apakah itu benar-benar melanggar konvensi?
Tidak. Karena dimasukkan ke dalam bahasa, konvensi dibangun dan mencakup (atau mengharuskan!) Penggunaan generator.
Apakah kompiler / juru bahasa bahasa pemrograman harus keluar dari konvensi apa pun untuk mengimplementasikan fitur tersebut
Seperti halnya fitur lain, kompiler hanya perlu dirancang untuk mendukung fitur tersebut. Dalam kasus Python, fungsi sudah objek dengan negara (seperti argumen default dan penjelasan fungsi).
apakah suatu bahasa harus mengimplementasikan multi-threading agar fitur ini berfungsi, atau dapatkah itu dilakukan tanpa teknologi threading?
Fakta menyenangkan: Implementasi Python default tidak mendukung threading sama sekali. Ini fitur Global Interpreter Lock (GIL), jadi tidak ada yang benar-benar berjalan bersamaan kecuali Anda sudah memutar proses kedua untuk menjalankan instance Python yang berbeda.
catatan: contoh dalam Python 3
Di luar Yield
Meskipun yield
kata kunci dapat digunakan dalam fungsi apa pun untuk mengubahnya menjadi generator, itu bukan satu-satunya cara untuk membuatnya. Python menampilkan Generator Expressions, cara yang ampuh untuk mengekspresikan generator dengan jelas dalam hal iterable lain (termasuk generator lain)
>>> pairs = ((x,y) for x in range(10) for y in range(10) if y >= x)
>>> pairs
<generator object <genexpr> at 0x0311DC90>
>>> sum(x*y for x,y in pairs)
1155
Seperti yang Anda lihat, tidak hanya sintaksnya yang bersih dan mudah dibaca, tetapi fungsi-fungsi sum
bawaannya seperti menerima generator.
Dengan
Lihat Proposal Peningkatan Python untuk pernyataan With . Ini sangat berbeda dari yang Anda harapkan dari pernyataan With dalam bahasa lain. Dengan sedikit bantuan dari perpustakaan standar, generator Python bekerja dengan indah sebagai manajer konteks untuk mereka.
>>> from contextlib import contextmanager
>>> @contextmanager
def debugWith(arg):
print("preprocessing", arg)
yield arg
print("postprocessing", arg)
>>> with debugWith("foobar") as s:
print(s[::-1])
preprocessing foobar
raboof
postprocessing foobar
Tentu saja, mencetak sesuatu adalah hal paling membosankan yang dapat Anda lakukan di sini, tetapi hal itu menunjukkan hasil yang terlihat. Opsi yang lebih menarik termasuk pengelolaan sumber daya secara otomatis (membuka dan menutup file / stream / koneksi jaringan), mengunci konkurensi, membungkus sementara atau mengganti suatu fungsi, dan mendekompresi kemudian mengkompres ulang data. Jika fungsi panggilan seperti menyuntikkan kode ke dalam kode Anda, maka dengan pernyataan seperti membungkus bagian dari kode Anda dengan kode lain. Bagaimanapun Anda menggunakannya, ini adalah contoh kuat dari pengait yang mudah ke dalam struktur bahasa. Generator berbasis hasil bukan satu-satunya cara untuk membuat manajer konteks, tetapi mereka pasti yang nyaman.
Untuk dan Kelelahan Sebagian
Untuk loop di Python bekerja dengan cara yang menarik. Mereka memiliki format berikut:
for <name> in <iterable>:
...
Pertama, ekspresi yang saya panggil <iterable>
dievaluasi untuk mendapatkan objek yang dapat diubah. Kedua, iterable telah __iter__
memanggilnya, dan iterator yang dihasilkan disimpan di belakang layar. Selanjutnya, __next__
dipanggil pada iterator untuk mendapatkan nilai untuk mengikat nama yang Anda masukkan <name>
. Langkah ini berulang sampai panggilan untuk __next__
melempar a StopIteration
. Pengecualian ditelan oleh for loop, dan eksekusi berlanjut dari sana.
Kembali ke generator: ketika Anda memanggil __iter__
generator, itu hanya mengembalikan sendiri.
>>> x = (a for a in "boring generator")
>>> id(x)
51502272
>>> id(x.__iter__())
51502272
Artinya, Anda dapat memisahkan iterasi atas sesuatu dari hal yang ingin Anda lakukan dengannya, dan mengubah perilaku itu di tengah jalan. Di bawah ini, perhatikan bagaimana generator yang sama digunakan dalam dua loop, dan pada yang kedua generator mulai mengeksekusi dari yang ditinggalkannya dari yang pertama.
>>> generator = (x for x in 'more boring stuff')
>>> for letter in generator:
print(ord(letter))
if letter > 'p':
break
109
111
114
>>> for letter in generator:
print(letter)
e
b
o
r
i
n
g
s
t
u
f
f
Evaluasi Malas
Salah satu kelemahan generator dibandingkan dengan daftar adalah satu-satunya hal yang dapat Anda akses dalam generator adalah hal berikutnya yang keluar darinya. Anda tidak dapat kembali dan untuk hasil sebelumnya, atau melompat ke depan untuk yang berikutnya tanpa melalui hasil antara. Sisi atas dari ini adalah generator dapat mengambil hampir tidak ada memori dibandingkan dengan daftar yang setara.
>>> import sys
>>> sys.getsizeof([x for x in range(10000)])
43816
>>> sys.getsizeof(range(10000000000))
24
>>> sys.getsizeof([x for x in range(10000000000)])
Traceback (most recent call last):
File "<pyshell#10>", line 1, in <module>
sys.getsizeof([x for x in range(10000000000)])
File "<pyshell#10>", line 1, in <listcomp>
sys.getsizeof([x for x in range(10000000000)])
MemoryError
Generator juga dapat dirantai dengan malas.
logfile = open("logs.txt")
lastcolumn = (line.split()[-1] for line in logfile)
numericcolumn = (float(x) for x in lastcolumn)
print(sum(numericcolumn))
Baris pertama, kedua, dan ketiga hanya mendefinisikan generator masing-masing, tetapi tidak melakukan pekerjaan nyata. Ketika baris terakhir dipanggil, jumlah meminta numericcolumn untuk suatu nilai, numericcolumn membutuhkan nilai dari lastcolumn, lastcolumn meminta nilai dari logfile, yang kemudian benar-benar membaca baris dari file. Tumpukan ini mengurai hingga jumlah mendapat bilangan bulat pertama. Kemudian, proses terjadi lagi untuk baris kedua. Pada titik ini, jumlah memiliki dua bilangan bulat, dan menambahkannya bersama. Perhatikan bahwa baris ketiga belum dibaca dari file. Sum kemudian melanjutkan meminta nilai dari numericcolumn (benar-benar tidak menyadari sisa rantai) dan menambahkannya, sampai numericcolumn habis.
Bagian yang sangat menarik di sini adalah bahwa garis-garisnya dibaca, dikonsumsi, dan dibuang secara individual. Pada titik tidak ada seluruh file dalam memori sekaligus. Apa yang terjadi jika file log ini, katakanlah, satu terabyte? Ini hanya berfungsi, karena hanya membaca satu baris pada satu waktu.
Kesimpulan
Ini bukan ulasan lengkap dari semua penggunaan generator di Python. Khususnya, saya melewatkan generator yang tidak terbatas, mesin negara, melewati nilai kembali, dan hubungan mereka dengan coroutine.
Saya percaya ini cukup untuk menunjukkan bahwa Anda dapat memiliki generator sebagai fitur bahasa yang terintegrasi dan bersih.
yield
pada dasarnya adalah mesin negara. Itu tidak dimaksudkan untuk mengembalikan hasil yang sama setiap kali. Apa yang akan dilakukannya dengan kepastian absolut adalah mengembalikan item berikutnya dalam jumlah setiap kali dipanggil. Thread tidak diperlukan; Anda perlu penutupan (lebih atau kurang), untuk mempertahankan kondisi saat ini.