Penjana dalam Python

Penjana dalam Python

Artikel ini menerangkan penjana dalam Python.

YouTube Video

Penjana dalam Python

Gambaran keseluruhan

Penjana dalam Python ialah sejenis iterator dan satu ciri yang berkuasa untuk melakukan pemprosesan berulang secara cekap. Ia membolehkan anda menulis kod yang cekap dari segi memori apabila berurusan dengan sejumlah besar data.

Apakah itu Penjana?

Penjana dalam Python ialah fungsi khas yang menghasilkan satu nilai pada satu masa, ditakrifkan dengan kata kunci yield. Ciri utamanya ialah ia menghentikan pelaksanaan sambil mengekalkan keadaannya dan boleh disambung semula kemudian.

Asas yield

yield ialah kata kunci yang memulangkan nilai dan menghentikan pelaksanaan fungsi pada masa yang sama.

 1def simple_generator():
 2    yield 1
 3    yield 2
 4    yield 3
 5
 6gen = simple_generator()
 7
 8print(next(gen))  # 1
 9print(next(gen))  # 2
10print(next(gen))  # 3
  • Apabila dipanggil, fungsi ini memulangkan objek penjana yang mengeluarkan nilai satu persatu.
  • Jika anda memanggil next() apabila tiada nilai seterusnya, ralat StopIteration akan berlaku.

next() dan StopIteration

 1def simple_generator():
 2    yield 1
 3    yield 2
 4    yield 3
 5
 6gen = simple_generator()
 7
 8try:
 9    while True:
10        value = next(gen)
11        print(value)
12except StopIteration:
13    print("Finished")
  • Dengan mengendalikan ralat StopIteration secara eksplisit seperti ini, anda boleh mengesan bila penjana telah selesai.

send(value)

Memanggil send(value) menyambung semula penjana dan menghantar value ke kedudukan ungkapan yield. Nilai yang dihantar boleh diterima di pihak penjana sebagai nilai pulangan bagi ungkapan yield. Pada panggilan pertama, anda tidak boleh menghantar apa-apa selain None dengan send(value), jadi anda mesti menggunakan next() atau send(None).

 1def gen():
 2    x = yield 1
 3    print(f"x = {x}")
 4    y = yield 2
 5    print(f"y = {y}")
 6
 7g = gen()
 8print(next(g))       # -> 1 (value from yield 1)
 9print(g.send(10))    # -> x = 10, 2 (value from yield 2)
10print(g.send(20))    # -> y = 20, StopIteration occurs
  • Dengan send(10), yield dalam penjana menjadi ungkapan yang mengembalikan 10, dan 10 diberikan kepada x.

throw()

Memanggil throw menyambung semula penjana dan melempar pengecualian pada kedudukan yield yang dihentikan sementara. Anda boleh mengendalikan pengecualian di dalam penjana untuk meneruskan pemprosesan. Jika pengecualian tidak ditangkap, ia akan tersebar keluar dan penjana akan berakhir.

 1def gen():
 2    try:
 3        yield 1
 4    except ValueError as e:
 5        print(f"Caught: {e}")
 6        yield "recovered"
 7
 8g = gen()
 9print(next(g))   # -> 1
10print(g.throw(ValueError("boom")))  # -> Caught: boom, "recovered"
  • Dalam kod ini, throw dipanggil untuk menyuntik pengecualian ke dalam penjana. Di pihak penjana, pengecualian dikendalikan dan recovered dipulangkan.

close()

Memanggil close() akan menamatkan penjana. Di dalam penjana, anda boleh menjalankan pembersihan menggunakan finally. Memanggil next() atau send() selepas memanggil close() akan menimbulkan ralat StopIteration.

1def gen():
2    try:
3        yield 1
4    finally:
5        print("Cleaning up...")
6
7g = gen()
8print(next(g))  # -> 1
9g.close()       # -> Cleaning up...
  • Kod ini menunjukkan bahawa memanggil close() akan menamatkan penjana dan mencetuskan proses pembersihan dalam finally.

yield from

yield from ialah sintaks yang digunakan untuk mendelegasikan kepada subpenjana. Ia adalah cara mudah untuk memanggil penjana lain di dalam penjana dan menyalurkan semua nilainya ke skop luar.

1def sub_gen():
2    yield 1
3    yield 2
4
5def main_gen():
6    yield from sub_gen()
7    yield 3
8
9print(list(main_gen()))  # -> [1, 2, 3]
  • Kod ini mendelegasikan semua nilai daripada subpenjana kepada penjana luar menggunakan yield from, dan kemudian menghasilkan 3.

Hubungan dengan Iterator

Penjana melaksanakan __iter__() dan __next__() secara dalaman, menjadikan mereka sejenis iterator. Oleh itu, ia serasi sepenuhnya dengan operasi boleh ulang seperti gelung for.

Integrasi dengan Gelung for

Dalam Python, gelung for menggunakan next() secara dalaman untuk mendapatkan nilai secara automatik.

1def simple_generator():
2    yield 1
3    yield 2
4    yield 3
5
6for value in simple_generator():
7    print(value)

Dengan kaedah ini, pengendalian StopIteration juga dilakukan secara automatik.

Mencipta Penjana Tak Terhingga

1def count_up(start=0):
2    while True:
3        yield start
4        start += 1
5
6counter = count_up()
7print(next(counter))  # 0
8print(next(counter))  # 1

Adalah mungkin untuk mencipta gelung tak terhingga, tetapi anda perlu berhati-hati apabila menggunakannya.

Ekspresi Penjana

Ekspresi penjana, yang ditulis menggunakan tanda kurung, membolehkan anda mentakrifkan penjana dengan sintaks yang serupa dengan pemahaman senarai.

1# List comprehension (generates the entire list at once)
2squares_list = [x**2 for x in range(5)]
3print(squares_list)
4
5# Generator expression
6squares_gen = (x**2 for x in range(5))
7for square in squares_gen:
8    print(square)

Tidak seperti pemahaman senarai, ia tidak memuatkan semua elemen ke dalam memori serentak, menjadikannya lebih cekap dari segi penggunaan memori.

Pengendalian Ralat dalam Penjana

Pengecualian boleh berlaku di dalam penjana. Dalam kes sedemikian, anda menggunakan try-except sama seperti dalam kod Python biasa.

 1def safe_divide_generator(numbers, divisor):
 2    """Yields results of dividing numbers by a given divisor safely."""
 3    for number in numbers:
 4        try:
 5            yield number / divisor  # Attempt to divide and yield result.
 6        except ZeroDivisionError:
 7            yield float('inf')  # Return infinity if division by zero occurs.
 8
 9# Example usage
10numbers = [10, 20, 30]
11gen = safe_divide_generator(numbers, 0)  # Create generator with divisor as 0.
12for value in gen:
13    print(value)  # Output: inf, inf, inf

Dalam contoh ini, pengendalian ralat yang betul dilakukan jika berlaku pembahagian dengan sifar.

Jejak Tindanan Penjana

Jika pengecualian berlaku di dalam penjana, ia akan dinaikkan apabila penjana disambung semula.

 1def error_generator():
 2    """A generator that yields values and raises an error."""
 3    yield 1
 4    raise ValueError("An error occurred")  # Raise a ValueError intentionally.
 5    yield 2
 6
 7gen = error_generator()
 8print(next(gen))       # Output: 1 (first value yielded)
 9try:
10    print(next(gen))   # Attempt to get the next value, which raises an error
11except ValueError as e:
12    print(e)           # Output: An error occurred (exception message is printed)
  • Penjana ini mengembalikan 1 terlebih dahulu. Ralat yang berlaku semasa menyambung semula ditangkap dan dipaparkan sebagai mesej ralat.

Contoh Penggunaan Penjana

Membaca fail baris demi baris (sesuai untuk fail yang besar)

1def read_large_file(filepath):
2    with open(filepath, 'r') as f:
3        for line in f:
4            yield line.strip()
  • Fungsi ini membaca fail teks baris demi baris menggunakan iterator, memotong ruang kosong dari setiap baris, dan mengembalikannya sebagai penjana, membolehkan fail besar diproses dengan penggunaan memori yang rendah.

Penjana untuk Jujukan Fibonacci

1def fibonacci(limit):
2    a, b = 0, 1
3    while a < limit:
4        yield a
5        a, b = b, a + b
6
7for n in fibonacci(100):
8    print(n)
  • Kod ini menggunakan penjana untuk menjana nombor Fibonacci secara berturutan yang kurang dari had atas dan mengeluarkannya menggunakan gelung for.

Kes Penggunaan

Penjana juga boleh digunakan dalam senario berikut.

  • Pemprosesan berurutan fail CSV atau log yang besar
  • Penomboran API
  • Memproses data penstriman (contohnya, Kafka, peranti IoT)

Ringkasan

Konsep Perkara Utama
yield Memberhentikan dan memulangkan nilai
Fungsi Penjana Fungsi yang mengandungi yield dan memulangkan iterator apabila dipanggil
Kelebihan Cekap memori dan sesuai untuk pemprosesan set data yang besar
Ekspresi Penjana Membenarkan sintaks ringkas seperti (x for x in iterable)

Dengan menggunakan penjana, anda boleh memproses set data yang besar dengan cekap sambil menjimatkan memori dan memastikan kod anda ringkas.

Anda boleh mengikuti artikel di atas menggunakan Visual Studio Code di saluran YouTube kami. Sila lihat juga saluran YouTube kami.

YouTube Video