Dalam tutorial ini, anda akan belajar bagaimana membuat lelaran dengan mudah menggunakan penjana Python, bagaimana ia berbeza dari iterator dan fungsi normal, dan mengapa anda harus menggunakannya.
Video: Penjana Python
Penjana di Python
Terdapat banyak kerja dalam membina iterator di Python. Kita harus melaksanakan kelas dengan __iter__()
dan __next__()
kaedah, menjejaki keadaan dalaman, dan meningkatkan StopIteration
apabila tidak ada nilai yang akan dikembalikan.
Ini panjang dan berlawanan dengan intuisi. Penjana datang untuk menyelamatkan dalam keadaan seperti itu.
Penjana Python adalah kaedah mudah untuk membuat iterator. Semua kerja yang kami sebutkan di atas dikendalikan secara automatik oleh generator di Python.
Secara ringkasnya, generator adalah fungsi yang mengembalikan objek (iterator) yang boleh kita lakukan berulang (satu nilai pada satu masa).
Buat Penjana di Python
Cukup mudah untuk membuat generator di Python. Semudah menentukan fungsi normal, tetapi dengan yield
pernyataan dan bukannya return
pernyataan.
Sekiranya fungsi mengandungi sekurang-kurangnya satu yield
pernyataan (mungkin mengandungi pernyataan lain yield
atau return
pernyataan), ia menjadi fungsi penjana. Keduanya yield
dan return
akan mengembalikan beberapa nilai dari fungsi.
Perbezaannya adalah bahawa sementara return
pernyataan menghentikan fungsi sepenuhnya, yield
pernyataan berhenti sementara fungsi menyimpan semua keadaannya dan kemudian berlanjutan dari sana pada panggilan berturut-turut.
Perbezaan antara fungsi Generator dan fungsi Normal
Inilah bagaimana fungsi penjana berbeza dari fungsi biasa.
- Fungsi penjana mengandungi satu atau lebih
yield
pernyataan. - Apabila dipanggil, ia mengembalikan objek (iterator) tetapi tidak segera memulakan pelaksanaan.
- Kaedah suka
__iter__()
dan__next__()
dilaksanakan secara automatik. Oleh itu, kita boleh melakukan lelaran melalui item yang menggunakannext()
. - Setelah fungsi menghasilkan, fungsi dijeda dan kawalan dipindahkan ke pemanggil.
- Pemboleh ubah tempatan dan keadaannya diingat antara panggilan berturut-turut.
- Akhirnya, apabila fungsi tersebut dihentikan,
StopIteration
dinaikkan secara automatik pada panggilan selanjutnya.
Berikut adalah contoh untuk menggambarkan semua perkara yang dinyatakan di atas. Kami mempunyai fungsi generator yang dinamakan my_gen()
dengan beberapa yield
pernyataan.
# A simple generator function def my_gen(): n = 1 print('This is printed first') # Generator function contains yield statements yield n n += 1 print('This is printed second') yield n n += 1 print('This is printed at last') yield n
Larian interaktif dalam jurubahasa diberikan di bawah. Jalankan ini di shell Python untuk melihat keluarannya.
>>> # It returns an object but does not start execution immediately. >>> a = my_gen() >>> # We can iterate through the items using next(). >>> next(a) This is printed first 1 >>> # Once the function yields, the function is paused and the control is transferred to the caller. >>> # Local variables and theirs states are remembered between successive calls. >>> next(a) This is printed second 2 >>> next(a) This is printed at last 3 >>> # Finally, when the function terminates, StopIteration is raised automatically on further calls. >>> next(a) Traceback (most recent call last):… StopIteration >>> next(a) Traceback (most recent call last):… StopIteration
Satu perkara menarik untuk diperhatikan dalam contoh di atas adalah bahawa nilai pemboleh ubah n diingat antara setiap panggilan.
Tidak seperti fungsi normal, pemboleh ubah tempatan tidak hancur ketika fungsi tersebut menghasilkan. Tambahan pula, objek penjana boleh diulang sekali sahaja.
Untuk memulakan semula proses, kita perlu membuat objek penjana lain menggunakan sesuatu seperti a = my_gen()
.
Satu perkara terakhir yang perlu diperhatikan ialah kita boleh menggunakan generator dengan gelung secara langsung.
Ini kerana for
gelung mengambil iterator dan mengulanginya menggunakan next()
fungsi. Secara automatik berakhir apabila StopIteration
dinaikkan. Lihat di sini untuk mengetahui bagaimana cara untuk loop sebenarnya dilaksanakan di Python.
# A simple generator function def my_gen(): n = 1 print('This is printed first') # Generator function contains yield statements yield n n += 1 print('This is printed second') yield n n += 1 print('This is printed at last') yield n # Using for loop for item in my_gen(): print(item)
Semasa anda menjalankan program, outputnya adalah:
Ini dicetak pertama 1 Ini dicetak kedua 2 Ini dicetak terakhir 3
Penjana Python dengan Gelung
Contoh di atas kurang digunakan dan kami mempelajarinya hanya untuk mendapatkan idea tentang apa yang berlaku di latar belakang.
Biasanya, fungsi penjana dilaksanakan dengan gelung yang mempunyai keadaan penamatan yang sesuai.
Mari kita ambil contoh penjana yang membalikkan rentetan.
def rev_str(my_str): length = len(my_str) for i in range(length - 1, -1, -1): yield my_str(i) # For loop to reverse the string for char in rev_str("hello"): print(char)
Pengeluaran
olleh
Dalam contoh ini, kami telah menggunakan range()
fungsi untuk mendapatkan indeks dalam urutan terbalik menggunakan loop untuk.
Catatan : Fungsi penjana ini tidak hanya berfungsi dengan rentetan, tetapi juga dengan jenis iterables lain seperti senarai, tuple, dll.
Ungkapan Penjana Python
Penjana ringkas boleh dibuat dengan cepat dengan menggunakan ungkapan penjana. Ia menjadikan penjana bangunan menjadi mudah.
Sama dengan fungsi lambda yang membuat fungsi tanpa nama, ungkapan penjana membuat fungsi penjana tanpa nama.
Sintaks untuk ekspresi penjana mirip dengan pemahaman senarai di Python. Tetapi tanda kurung persegi diganti dengan tanda kurung bulat.
Perbezaan utama antara pemahaman senarai dan ekspresi penjana adalah bahawa pemahaman daftar menghasilkan keseluruhan senarai sementara ekspresi penjana menghasilkan satu item pada satu masa.
Mereka mempunyai pelaksanaan yang malas (menghasilkan barang hanya apabila diminta). Atas sebab ini, ungkapan penjana jauh lebih cekap memori daripada pemahaman senarai yang setara.
# Initialize the list my_list = (1, 3, 6, 10) # square each term using list comprehension list_ = (x**2 for x in my_list) # same thing can be done using a generator expression # generator expressions are surrounded by parenthesis () generator = (x**2 for x in my_list) print(list_) print(generator)
Pengeluaran
(1, 9, 36, 100)
Kita dapat melihat di atas bahawa ungkapan penjana tidak menghasilkan hasil yang diperlukan dengan segera. Sebaliknya, ia mengembalikan objek penjana, yang menghasilkan barang hanya berdasarkan permintaan.
Inilah cara kita mula mendapatkan item dari penjana:
# Initialize the list my_list = (1, 3, 6, 10) a = (x**2 for x in my_list) print(next(a)) print(next(a)) print(next(a)) print(next(a)) next(a)
Apabila kita menjalankan program di atas, kita mendapat output berikut:
1 9 36 100 Jejak Balik (panggilan terakhir terakhir): Fail "", baris 15, dalam StopIteration
Ungkapan penjana boleh digunakan sebagai argumen fungsi. Apabila digunakan sedemikian rupa, kurungan bulat dapat dijatuhkan.
>>> sum(x**2 for x in my_list) 146 >>> max(x**2 for x in my_list) 100
Penggunaan Penjana Python
Terdapat beberapa sebab yang menjadikan generator sebagai pelaksanaan yang kuat.
1. Mudah Dilaksanakan
Penjana dapat dilaksanakan dengan cara yang jelas dan ringkas berbanding dengan rakan kelas iteratornya. Berikut adalah contoh untuk melaksanakan urutan kuasa 2 menggunakan kelas iterator.
class PowTwo: def __init__(self, max=0): self.n = 0 self.max = max def __iter__(self): return self def __next__(self): if self.n> self.max: raise StopIteration result = 2 ** self.n self.n += 1 return result
Program di atas adalah panjang dan membingungkan. Sekarang, mari kita melakukan perkara yang sama menggunakan fungsi penjana.
def PowTwoGen(max=0): n = 0 while n < max: yield 2 ** n n += 1
Oleh kerana penjana menyimpan maklumat secara automatik, pelaksanaannya ringkas dan lebih bersih.
2. Memori Berkesan
Fungsi normal untuk mengembalikan urutan akan membuat keseluruhan urutan dalam memori sebelum mengembalikan hasilnya. Ini adalah jumlah yang berlebihan, jika jumlah item dalam urutannya sangat besar.
Pelaksanaan penjujukan urutan sedemikian mesra memori dan lebih disukai kerana hanya menghasilkan satu item pada satu masa.
3. Mewakili Aliran Tidak Terbatas
Penjana adalah media yang sangat baik untuk mewakili aliran data yang tidak terhingga. Aliran tak terbatas tidak dapat disimpan dalam memori, dan kerana penjana menghasilkan hanya satu item pada satu masa, aliran tersebut dapat mewakili aliran data yang tidak terbatas.
Fungsi penjana berikut dapat menghasilkan semua nombor genap (sekurang-kurangnya secara teori).
def all_even(): n = 0 while True: yield n n += 2
4. Penjana Paip
Pelbagai generator boleh digunakan untuk mengatur rangkaian operasi. Ini digambarkan dengan lebih baik menggunakan contoh.
Katakan kita mempunyai penjana yang menghasilkan nombor dalam siri Fibonacci. Dan kami mempunyai penjana lain untuk kuasa dua nombor.
Sekiranya kita ingin mengetahui jumlah petak nombor dalam siri Fibonacci, kita dapat melakukannya dengan cara berikut dengan menyatukan output fungsi penjana bersama-sama.
def fibonacci_numbers(nums): x, y = 0, 1 for _ in range(nums): x, y = y, x+y yield x def square(nums): for num in nums: yield num**2 print(sum(square(fibonacci_numbers(10))))
Pengeluaran
4895
Penyambungan saluran paip ini cekap dan mudah dibaca (dan ya, lebih sejuk!).