Generator

Pada artikel ini, kita akan belajar cara menciptakan iterasi menggunakan generator, apa perbedaan generator dengan iterator dan fungsi, dan mengapa kita harus menggunakan generator.

 

Tabel Konten


Pengertian Generator

Ada banyak hal yang berlebihan (overhead) di dalam membuat iterator di Python. Pertama, kita harus membuat kelas yang menggunakan metode __iter__() dan __next__() . Kemudian kita juga harus menjaga state dari item, dan harus membangkitkan StopIteration pada saat tidak ada lagi item yang tersedia.

Hal ini agak panjang dan rumit. Dalam hal ini, untuk menghindari hal tersebut, kita bisa menggunakan generator.

Generator di Python adalah cara sederhana membuat iterator. Semua overhead yang kita sebutkan di atas, akan ditangani secara otomatis oleh generator.

Singkatnya, generator adalah fungsi yang mengembalikan sebuah objek iterator yang padanya bisa kita lakukan iterasi (satu nilai per satu waktu).


Cara Membuat Generator di Python

Cukup mudah untuk membuat generator di Python. Mirip dengan membuat fungsi biasa. Hanya saja, kita menggantikan pernyataan return dengan yield.

Fungsi yang memiliki minimal satu yield (fungsi bisa berisi lebih dari satu yield atau return), akan menjadi fungsi generator. yield maupun return sama – sama berfungsi mengembalikan suatu nilai dari sebuah fungsi.

Perbedaan return dan yield adalah, return akan menghentikan (terminasi) fungsi secara keseluruhan, sementara yield hanya akan menghentikan sementara (pause) fungsi dan menyimpan semua state variabel yang di dalamnya untuk nantinya bisa dilanjutkan kembali dari state tersebut.


Perbedaan Fungsi Generator dan Fungsi Biasa

Berikut adalah perbedaan fungsi generator dengan fungsi biasa:

  • Fungsi generator berisi satu atau lebih pernyataan yield
  • Pada saat dipanggil, fungsi generator akan mengembalikan objek iterator, tapi tidak langsung dieksekusi.
  • Metode __iter__() dan __next__() sudah diimplementasikan secara otomatis. Jadi kita bisa langsung melakukan iterasi dengan fungsi next().
  • Sekali fungsi menemui yield, fungsi akan pause dan kendali ditransfer kembali ke pemanggil.
  • Variabel lokal dan state-nya diingat untuk pemanggilan selanjutnya.
  • Terakhir, pada saat fungsi diterminasi (dihentikan total), StopIteration dipanggil secara otomatis.

Berikut adalah contoh generator dengan beberapa pernyataan yield

# Fungsi generator sederhana def my_gen(): n = 1 print('This is printed first') # Fungsi generator berisi pernyataan yield yield n n += 1 print('This is printed second') yield n n += 1 print('This is printed at last') yield n

Selanjutnya, hasilnya bisa dijalankan pada mode interaktif seperti berikut:

>>> # my_gen() mengembalikan objek iterator, tapi tidak langsung mengeksekusinya
>>> a = my_gen()

>>> # Kita bisa melakukan iterasi pertama menggunakan next()
>>> next(a)
This is printed first
1
>>> # Sekali fungsi menemukan yield, fungsi akan dipause
>>> # dan kendali dikembalikan ke pemanggil
>>> # Variabel lokal dan state-nya akan diingat untuk pemanggilan selanjutnya
>>> next(a)
This is printed second
2

>>> next(a)
This is printed at last
3

>>> Terakhir, fungsi diterminasi, StopIterasi otomatis dibangkitkan
>>> next(a)
Traceback (most recent call last):
...
StopIteration

Salah satu hal yang perlu diperhatikan untuk contoh di atas adalah, nilai dari variabel n tetap diingat pada tiap pemanggilan fungsi.

Berbeda dengan fungsi normal, variabel lokal tidak akan dihancurkan pada saat pernyataan yield. Selain itu, objek generator hanya bisa diiterasi untuk sekali saja. Untuk mengulang kembali proses, kita harus membuat generator lain menggunakan pernyataan seperti misalnya a = my_gen().

Kita juga bisa menggunakan generator di dalam loop for secara langsung. Hal ini karena loop for memakai iterator dan melakukan iterasi padanya menggunakan fungsi next(). StopIteration otomatis dilakukan pada akhir iterasi.

# Fungsi generator sederhana def my_gen(): n = 1 print('This is printed first') # Fungsi generator berisi pernyataan yield yield n n += 1 print('This is printed second') yield n n += 1 print('This is printed at last') yield n # Menggunakan loop for for item in my_gen(): print(item)

Pada saat contoh di atas dijalankan, hasilnya akan tampak seperti berikut:

This is printed first
1
This is printed second
2
This is printed at last
3

Generator Menggunakan Loop

Contoh-contoh di atas jarang dipakai. Kita mempelajarinya hanya untuk tahu teknisnya apa yang sebenarnya terjadi pada generator.

Umumnya, fungsi generator diterapkan pada loop yang memiliki kondisi terminasi (penghentian) yang sesuai.

Mari kita buat contoh generator yang berfungsi membalikkan sebuah string.

  def rev_str(my_str): length = len(my_str) for i in range(length - 1,-1,-1): yield my_str[i] # Loop for untuk membalikkan string # Output: # o # l # l # e # h for char in rev_str("hello"): print(char)

Pada contoh di atas, kita menggunakan fungsi range() untuk mendapatkan indeks secara terbalik yang digunakan dalam loop for.

Fungsi generator seperti itu tidak hanya berlaku untuk string, tapi juga untuk iterable lainnya seperti list, tuple, dan lain – lain.


Generator Expression

Generator sederhana bisa dibuat dengan menggunakan generator expression. Dengan generator expression ini, pembuatan generator menjadi mudah.

Mirip dengan fungsi lambda yang membuat fungsi anonim, generator expression juga membuat sebuah fungsi generator anonim.

Sintaks dari generator expression mirip dengan list comprehension. Hanya saja tanda kurung [ ] digantikan dengan tanda kurung ( ).

Perbedaan utama antara list comprehension dengan generator expression adalah, list comprehension langsung menghasilkan keseluruhan anggota list, sedangkan generator expression menghasilkannya satu item per satu waktu.

Tipe seperti generator expression ini sering disebut tipe lazy (malas), karena hanya memproduksi item pada saat diminta. Hal inilah yang menyebabkan generator expression lebih hemat memori ketimbang list comprehension.

# Membuat list my_list = [1, 3, 6, 10] # Tanda [ ] untuk list comprehension # Output: [1, 9, 36, 100] [x**2 for x in my_list] # Hal yang sama bisa menggunakan generator expression # Output: generator object genexpr at 0x0000000002EBDAF8 (x**2 for x in my_list)

Kita bisa lihat di atas, bahwa generator expression tidak langsung memproduksi hasil secara keseluruhan. Generator expression ini hanya mengembalikan item bila diminta.

# Membuat list my_list = [1, 3, 6, 10] a = (x**2 for x in my_list) # Output: 1 print(next(a)) # Output: 9 print(next(a)) # Output: 36 print(next(a)) # Output: 100 print(next(a)) # Output: StopIteration next(a)

Generator expression juga bisa digunakan sebagai argumen fungsi. Bila demikian, tanda kurungnya bisa dihilangkan. Misalnya pada fungsi sum() dan max() seperti berikut:

>>> my_list = [1, 3, 6, 10]
>>> sum(x**2 for x in my_list)
146
>>> max(x**2 for x in my_list)
100

Alasan Penggunaan Generator

Ada beberapa hal yang menjadi alasan penggunaan generator, yaitu sebagai berikut:

1. Mudah diterapkan

Generator bisa diterapkan dengan lebih mudah dan ringkas dibandingkan dengan kelas iterator. Berikut ini contoh penggunaan kelas iterator untuk menghasilkan deret bilangan pangkat 2 dari 0 sampai yang bilangan ditentukan.

class PowTwo:
    def __init__(self, max=0):
        self.max = max

    def __iter__(self):
        self.n = 0
        return self

    def __next__(self):
        if self.n > self.max:
            raise StopIteration

        result = 2 ** self.n
        self.n += 1
        return result

Dan berikut hasilnya sama dengan menggunakan fungsi generator.

def PowTwoGen(max = 0):
    n = 0
    while n < max:
        yield 2 ** n
        n += 1

Dari contoh di atas, terlihat bahwa penggunaan generator lebih mudah dan ringkas dibandingkan iterator.

2. Lebih Hemat Memori

Fungsi biasa yang dibuat untuk menghasilkan sequence, akan menyimpan keseluruhan sequence sebelum mengembalikan hasilnya. Hal ini akan menghabiskan memori bila jumlah itemnya sangat besar.

Sementara itu, generator jauh lebih hemat memori karena hanya memproduksi item satu persatu yaitu hanya saat dipanggil.

3. Merepresentasikan Stream Tak Berhingga (Infinite Stream)

Generator adalah cara yang paling bagus untuk mewakili aliran data yang tak berhingga. Stream (aliran) data yang tak berhingga, tidak bisa disimpan dalam memori, sehingga cocok menggunakan generator.

Berikut adalah contoh untuk membangkitkan semua bilangan genap yang ada (setidaknya secara teoritis saja)

def all_even():
    n = 0
    while True:
        yield n
        n += 2

4. Pipelining Generator

Pipeline adalah menjadikan input dari sebuah proses menjadi proses dari operasi yang lain. Generator bisa digunakan untuk pipeline dari serangkaian operasi. Hal ini akan lebih jelas bila diilustrasikan dengan contoh.

Anggaplah kita mempunyai sebuah file log dari rantai makanan cepat saji terkenal. File log ini memiliki sebuah kolom (kolom ke-4) yang mencatat jumlah pizza yang terjual setiap jam dan kita ingin menjumlahkannya untuk mendapatkan jumlah pizza yang terjual dalam 5 tahun.

Kita asumsikan bahwa semua yang kosong di kolom ke 4 sebagai 'N/A'. Maka, generator yang bisa diterapkan untuk hal ini adalah seperti berikut.

with open('penjualan.log') as file:
    pizza_col = (line[3] for line in file)
    per_jam = (int(x) for x in pizza_col if x != 'N/A')
    print("Total pizza terjual = ", sum(per_jam))

Pipelining menjadikan kode menjadi lebih efisien dan mudah dibaca.

 

Bagikan:

Tinggalkan Balasan