Saya mulai belajar Python dan saya menemukan fungsi generator, yang memiliki pernyataan hasil di dalamnya. Saya ingin tahu jenis masalah apa yang fungsi-fungsi ini benar-benar pecahkan.
Saya mulai belajar Python dan saya menemukan fungsi generator, yang memiliki pernyataan hasil di dalamnya. Saya ingin tahu jenis masalah apa yang fungsi-fungsi ini benar-benar pecahkan.
Jawaban:
Generator memberi Anda evaluasi malas. Anda menggunakannya dengan mengulanginya, baik secara eksplisit dengan 'untuk' atau secara implisit dengan meneruskannya ke fungsi apa pun atau membangun yang diulanginya. Anda dapat menganggap generator sebagai mengembalikan beberapa item, seolah-olah mereka mengembalikan daftar, tetapi alih-alih mengembalikannya sekaligus, mereka mengembalikannya satu per satu, dan fungsi generator dijeda hingga item berikutnya diminta.
Generator bagus untuk menghitung set besar hasil (khususnya perhitungan yang melibatkan loop sendiri) di mana Anda tidak tahu apakah Anda akan membutuhkan semua hasil, atau di mana Anda tidak ingin mengalokasikan memori untuk semua hasil pada saat yang sama . Atau untuk situasi di mana generator menggunakan generator lain , atau mengkonsumsi sumber daya lain, dan itu lebih nyaman jika itu terjadi selambat mungkin.
Penggunaan lain untuk generator (yang benar-benar sama) adalah untuk mengganti panggilan balik dengan iterasi. Dalam beberapa situasi Anda ingin fungsi melakukan banyak pekerjaan dan sesekali melaporkan kembali ke pemanggil. Secara tradisional Anda akan menggunakan fungsi panggilan balik untuk ini. Anda meneruskan panggilan balik ini ke fungsi-kerja dan secara berkala akan memanggil panggilan balik ini. Pendekatan generator adalah bahwa fungsi-kerja (sekarang generator) tidak tahu apa-apa tentang panggilan balik, dan hanya menghasilkan kapan pun ia ingin melaporkan sesuatu. Penelepon, alih-alih menulis callback terpisah dan meneruskannya ke fungsi-fungsi, melakukan semua pelaporan dengan sedikit putaran 'untuk' di sekitar generator.
Misalnya, Anda menulis program 'pencarian sistem file'. Anda dapat melakukan pencarian secara keseluruhan, mengumpulkan hasil dan kemudian menampilkannya satu per satu. Semua hasil harus dikumpulkan sebelum Anda menunjukkan yang pertama, dan semua hasil akan di memori pada saat yang sama. Atau Anda bisa menampilkan hasilnya saat Anda menemukannya, yang akan lebih hemat memori dan lebih ramah terhadap pengguna. Yang terakhir dapat dilakukan dengan melewatkan fungsi pencetakan hasil ke fungsi pencarian sistem file, atau bisa dilakukan dengan hanya membuat fungsi pencarian generator dan mengulangi hasilnya.
Jika Anda ingin melihat contoh dari dua pendekatan terakhir, lihat os.path.walk () (fungsi berjalan sistem file lama dengan callback) dan os.walk () (generator berjalan sistem file baru.) Tentu saja, jika Anda benar-benar ingin mengumpulkan semua hasil dalam daftar, pendekatan generator sepele untuk dikonversi ke pendekatan daftar besar:
big_list = list(the_generator)
yield
dan join
sesudahnya untuk mendapatkan hasil selanjutnya, ia tidak mengeksekusi secara paralel (dan tidak ada generator perpustakaan standar yang melakukan ini; diam-diam meluncurkan utas tidak disukai). Generator berhenti di masing-masing yield
hingga nilai berikutnya diminta. Jika generator membungkus I / O, OS mungkin secara proaktif menyimpan data dari file dengan asumsi itu akan diminta segera, tapi itu OS, Python tidak terlibat.
Salah satu alasan untuk menggunakan generator adalah untuk membuat solusi lebih jelas untuk beberapa jenis solusi.
Yang lain adalah memperlakukan hasil satu per satu, menghindari membuat daftar hasil yang sangat besar yang akan Anda proses pisahkan.
Jika Anda memiliki fungsi fibonacci-up-to-n seperti ini:
# function version
def fibon(n):
a = b = 1
result = []
for i in xrange(n):
result.append(a)
a, b = b, a + b
return result
Anda dapat lebih mudah menulis fungsi karena ini:
# generator version
def fibon(n):
a = b = 1
for i in xrange(n):
yield a
a, b = b, a + b
Fungsinya lebih jelas. Dan jika Anda menggunakan fungsi seperti ini:
for x in fibon(1000000):
print x,
dalam contoh ini, jika menggunakan versi generator, seluruh daftar item 1000000 tidak akan dibuat sama sekali, hanya satu nilai pada suatu waktu. Itu tidak akan menjadi kasus ketika menggunakan versi daftar, di mana daftar akan dibuat terlebih dahulu.
list(fibon(5))
Lihat bagian "Motivasi" di PEP 255 .
Penggunaan generator yang tidak jelas adalah menciptakan fungsi yang dapat terputus, yang memungkinkan Anda melakukan hal-hal seperti memperbarui UI atau menjalankan beberapa pekerjaan "secara bersamaan" (disisipkan, sebenarnya) tanpa menggunakan utas.
Saya menemukan penjelasan ini yang menghilangkan keraguan saya. Karena ada kemungkinan orang yang tidak tahu Generators
juga tidak tahuyield
Kembali
Pernyataan pengembalian adalah tempat semua variabel lokal dihancurkan dan nilai yang dihasilkan dikembalikan (dikembalikan) ke pemanggil. Jika fungsi yang sama dipanggil beberapa waktu kemudian, fungsi tersebut akan mendapatkan serangkaian variabel baru.
Menghasilkan
Tetapi bagaimana jika variabel lokal tidak dibuang ketika kita keluar dari suatu fungsi? Ini menyiratkan bahwa kita bisa di resume the function
mana kita tinggalkan. Di sinilah konsep generators
diperkenalkan dan yield
pernyataan dilanjutkan di mana yang function
ditinggalkan.
def generate_integers(N):
for i in xrange(N):
yield i
In [1]: gen = generate_integers(3)
In [2]: gen
<generator object at 0x8117f90>
In [3]: gen.next()
0
In [4]: gen.next()
1
In [5]: gen.next()
Jadi itulah perbedaan antara return
dan yield
pernyataan dalam Python.
Pernyataan hasil adalah apa yang membuat fungsi menjadi fungsi generator.
Jadi generator adalah alat sederhana dan kuat untuk membuat iterator. Mereka ditulis seperti fungsi biasa, tetapi mereka menggunakan yield
pernyataan kapan pun mereka ingin mengembalikan data. Setiap kali next () dipanggil, generator melanjutkan di tempat yang ditinggalkannya (ia mengingat semua nilai data dan pernyataan mana yang terakhir dieksekusi).
Katakanlah Anda memiliki 100 juta domain di tabel MySQL Anda, dan Anda ingin memperbarui peringkat Alexa untuk setiap domain.
Hal pertama yang Anda butuhkan adalah memilih nama domain Anda dari database.
Katakanlah nama tabel Anda adalah domains
dan nama kolom adalah domain
.
Jika Anda menggunakannya SELECT domain FROM domains
akan mengembalikan 100 juta baris yang akan menghabiskan banyak memori. Jadi server Anda mungkin macet.
Jadi Anda memutuskan untuk menjalankan program dalam batch. Katakanlah ukuran batch kami adalah 1000.
Dalam batch pertama kami, kami akan meminta 1000 baris pertama, memeriksa peringkat Alexa untuk setiap domain dan memperbarui baris database.
Dalam batch kedua kami, kami akan bekerja pada 1000 baris berikutnya. Dalam batch ketiga kami akan dari 2001 hingga 3000 dan seterusnya.
Sekarang kita membutuhkan fungsi generator yang menghasilkan batch kita.
Inilah fungsi generator kami:
def ResultGenerator(cursor, batchsize=1000):
while True:
results = cursor.fetchmany(batchsize)
if not results:
break
for result in results:
yield result
Seperti yang Anda lihat, fungsi kami menyimpan yield
hasilnya. Jika Anda menggunakan kata kunci return
alih-alih yield
, maka seluruh fungsi akan berakhir setelah mencapai kembali.
return - returns only once
yield - returns multiple times
Jika suatu fungsi menggunakan kata kunci yield
maka itu adalah generator.
Sekarang Anda dapat mengulangi seperti ini:
db = MySQLdb.connect(host="localhost", user="root", passwd="root", db="domains")
cursor = db.cursor()
cursor.execute("SELECT domain FROM domains")
for result in ResultGenerator(cursor):
doSomethingWith(result)
db.close()
Buffering. Ketika efisien untuk mengambil data dalam potongan besar, tetapi memprosesnya dalam potongan kecil, maka generator mungkin membantu:
def bufferedFetch():
while True:
buffer = getBigChunkOfData()
# insert some code to break on 'end of data'
for i in buffer:
yield i
Di atas memungkinkan Anda dengan mudah memisahkan buffering dari pemrosesan. Fungsi konsumen sekarang bisa mendapatkan nilai satu per satu tanpa khawatir tentang buffering.
Saya telah menemukan bahwa generator sangat membantu dalam membersihkan kode Anda dan dengan memberi Anda cara yang sangat unik untuk merangkum dan memodulasi kode. Dalam situasi di mana Anda perlu sesuatu untuk terus memuntahkan nilai-nilai berdasarkan proses internal sendiri dan ketika bahwa kebutuhan sesuatu yang disebut dari mana saja di kode Anda (dan bukan hanya dalam loop atau blok misalnya), generator yang fitur untuk menggunakan.
Contoh abstrak akan menjadi penghasil angka Fibonacci yang tidak hidup dalam satu lingkaran dan ketika dipanggil dari mana saja akan selalu mengembalikan angka berikutnya dalam urutan:
def fib():
first = 0
second = 1
yield first
yield second
while 1:
next = first + second
yield next
first = second
second = next
fibgen1 = fib()
fibgen2 = fib()
Sekarang Anda memiliki dua objek penghasil angka Fibonacci yang dapat Anda panggil dari mana saja dalam kode Anda dan mereka akan selalu mengembalikan angka Fibonacci yang lebih besar secara berurutan sebagai berikut:
>>> fibgen1.next(); fibgen1.next(); fibgen1.next(); fibgen1.next()
0
1
1
2
>>> fibgen2.next(); fibgen2.next()
0
1
>>> fibgen1.next(); fibgen1.next()
3
5
Hal yang indah tentang generator adalah bahwa mereka merangkum keadaan tanpa harus melalui lingkaran menciptakan objek. Salah satu cara berpikir tentang mereka adalah sebagai "fungsi" yang mengingat keadaan internal mereka.
Saya mendapatkan contoh Fibonacci dari Python Generator - Apa itu? dan dengan sedikit imajinasi, Anda dapat menemukan banyak situasi lain di mana generator membuat alternatif yang bagus untuk for
loop dan konstruksi iterasi tradisional lainnya.
Penjelasan sederhana: Pertimbangkan sebuah for
pernyataan
for item in iterable:
do_stuff()
Banyak waktu, semua item di iterable
tidak perlu ada di sana sejak awal, tetapi dapat dihasilkan dengan cepat sesuai kebutuhan. Ini bisa menjadi jauh lebih efisien di keduanya
Di lain waktu, Anda bahkan tidak tahu semua item sebelumnya. Sebagai contoh:
for command in user_input():
do_stuff_with(command)
Anda tidak memiliki cara untuk mengetahui semua perintah pengguna sebelumnya, tetapi Anda dapat menggunakan loop yang bagus seperti ini jika Anda memiliki generator yang memberi Anda perintah:
def user_input():
while True:
wait_for_command()
cmd = get_command()
yield cmd
Dengan generator Anda juga dapat memiliki iterasi lebih dari urutan yang tak terbatas, yang tentu saja tidak mungkin ketika iterasi di atas kontainer.
Penggunaan favorit saya adalah "filter" dan "kurangi" operasi.
Katakanlah kita sedang membaca file, dan hanya ingin baris yang dimulai dengan "##".
def filter2sharps( aSequence ):
for l in aSequence:
if l.startswith("##"):
yield l
Kita kemudian dapat menggunakan fungsi generator dalam loop yang tepat
source= file( ... )
for line in filter2sharps( source.readlines() ):
print line
source.close()
Contoh pengurangannya serupa. Katakanlah kita memiliki file di mana kita perlu menemukan blok <Location>...</Location>
garis. [Bukan tag HTML, tapi garis yang terlihat seperti tag.]
def reduceLocation( aSequence ):
keep= False
block= None
for line in aSequence:
if line.startswith("</Location"):
block.append( line )
yield block
block= None
keep= False
elif line.startsWith("<Location"):
block= [ line ]
keep= True
elif keep:
block.append( line )
else:
pass
if block is not None:
yield block # A partial block, icky
Sekali lagi, kita bisa menggunakan generator ini untuk loop yang tepat.
source = file( ... )
for b in reduceLocation( source.readlines() ):
print b
source.close()
Idenya adalah bahwa fungsi generator memungkinkan kita untuk menyaring atau mengurangi urutan, menghasilkan urutan lain satu nilai pada satu waktu.
fileobj.readlines()
akan membaca seluruh file ke daftar di memori, mengalahkan tujuan menggunakan generator. Karena objek file sudah dapat diubah, Anda dapat menggunakannya for b in your_generator(fileobject):
. Dengan begitu file Anda akan dibaca satu baris pada satu waktu, untuk menghindari membaca seluruh file.
Contoh praktis di mana Anda dapat menggunakan generator adalah jika Anda memiliki semacam bentuk dan Anda ingin beralih di sudut, tepi atau apa pun. Untuk proyek saya sendiri (kode sumber di sini ) saya memiliki sebuah persegi panjang:
class Rect():
def __init__(self, x, y, width, height):
self.l_top = (x, y)
self.r_top = (x+width, y)
self.r_bot = (x+width, y+height)
self.l_bot = (x, y+height)
def __iter__(self):
yield self.l_top
yield self.r_top
yield self.r_bot
yield self.l_bot
Sekarang saya bisa membuat persegi panjang dan loop di sudut-sudutnya:
myrect=Rect(50, 50, 100, 100)
for corner in myrect:
print(corner)
Alih-alih __iter__
Anda bisa memiliki metode iter_corners
dan menyebutnya dengan for corner in myrect.iter_corners()
. Hanya saja lebih elegan untuk digunakan __iter__
karena kita bisa menggunakan nama instance kelas secara langsung dalam for
ekspresi.
Namun, beberapa jawaban yang bagus di sini, saya juga merekomendasikan pembacaan lengkap tutorial Pemrograman Fungsional Python yang membantu menjelaskan beberapa kasus penggunaan generator yang lebih kuat.
Karena metode pengiriman generator belum disebutkan, berikut adalah contohnya:
def test():
for i in xrange(5):
val = yield
print(val)
t = test()
# Proceed to 'yield' statement
next(t)
# Send value to yield
t.send(1)
t.send('2')
t.send([3])
Ini menunjukkan kemungkinan untuk mengirim nilai ke generator yang sedang berjalan. Kursus yang lebih maju tentang generator dalam video di bawah ini (termasukyield
dari eksplorasi, generator untuk pemrosesan paralel, lolos dari batas rekursi, dll.)
Tumpukan barang. Kapan saja Anda ingin membuat urutan item, tetapi tidak ingin harus 'mematerialisasikan' semuanya menjadi daftar sekaligus. Misalnya, Anda dapat memiliki generator sederhana yang mengembalikan bilangan prima:
def primes():
primes_found = set()
primes_found.add(2)
yield 2
for i in itertools.count(1):
candidate = i * 2 + 1
if not all(candidate % prime for prime in primes_found):
primes_found.add(candidate)
yield candidate
Anda kemudian dapat menggunakannya untuk menghasilkan produk dari bilangan prima berikutnya:
def prime_products():
primeiter = primes()
prev = primeiter.next()
for prime in primeiter:
yield prime * prev
prev = prime
Ini adalah contoh yang cukup sepele, tetapi Anda dapat melihat bagaimana hal itu berguna untuk memproses dataset besar (berpotensi tak terbatas!) Tanpa membuatnya terlebih dahulu, yang hanya salah satu kegunaan yang lebih jelas.
Juga bagus untuk mencetak bilangan prima hingga n:
def genprime(n=10):
for num in range(3, n+1):
for factor in range(2, num):
if num%factor == 0:
break
else:
yield(num)
for prime_num in genprime(100):
print(prime_num)