Skip to content

Comprehensions and Generators

Pada bagian ini saya menuliskan catatan yang bertujuan bukan semata untuk meningkat performa dari kode yang dibuat namun juga tetap mempertahankan kode yang mudah untuk dibaca dan mudah untuk di maintain. Sehingga catatan ini secar umum membahas

  • Fungsi map(), zip(), dan filter()
  • Comprehensions
  • Generators

InsyaAllahk kita akan melakukan beberapa perhitungan dan perbandingan terhadap beberapa kode yang dicatat dan membuat kesimpulan dari perhitungan dan perbandingan tersebut, terutama pada waktu yang dibutuhkan mesin untuk menajalankan kode.

Coba perhatikan kode dibawah ini. Kedua fungsi ini mengembalikan akar kuadrat dari parameter yang diberikan.

Code

%%timeit
def square_satu(n):
    return n**2

square_satu(10)
338 ns ± 14.8 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
%%timeit
def square_dua(n):
    return n*n

square_satu(10)
127 ns ± 1.06 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)

Fungsi diatas sama-sama mengembalikan nilai akar pangkat dari parameter yang diberikan. Sekilas fungsi square_dua() lebih cepat. Namun kebanyakan kita tidak terlalu memikirkan performa yang sedikit berbeda. Dalam kasus diatas tidak masalah jika mengutaman demikian, lagi pula fungsi square_satu() lebih mudah dibaca. Jadi kapan sebuah performa menjadi isu yang diutamakan ? Yaitu ketika kita berhubungan dengan data yang amat besar .

Misalkan kita menggunakan kedua fungsi diatas ke data yang memliki 10 juta baris. Jika perbedaan dari fungsi tersebut misalkan 0.1 detik. Sehingga jika diaplikasikan ke data tersebut maka akan memliki rentang waktu 27 jam, Cukup signifikan, dan disinilah kita harus mempertimbangkan masalah performa.

Jadi dibawah ini saya mencatat eksplorasi tentang iterators yang menghemat penyimpanan dalam menjalankan operasi sederhana dari sebuah kumpulan data secara sekaligus.

Fungsi map, zip, dan filter

Dibawah ini saya mencatat cara menggunakan fungsi map, zip dan filter serta bagaimana cara menggunakan generators dan comprehension untuk menghasilkan nilai yang sama menggunakan ketiga fungsi diatas.

Map

Arti map dari dokumentasi resmi python.

Apa itu Map

map(function, iterable, …)

Return an iterator that applies function to every item of iterable, yielding the results. If additional iterable arguments are passed, function must take that many arguments and is applied to the items from all iterables in parallel. With multiple iterables, the iterator stops when the shortest iterable is exhausted.

InsyaAllah kita akan bahas tentang yield pada bagian catatan dibawah ini.

Bahasa catatan saya, fungsi map ini memiliki parameter 2 parameter wajib dan opsinal. Dimana fungsi ini akan mengembalikan nilai dalam bentuk iterator hasil dari operasional fungsi pada parameter pertama atas iterable. Jika parameter diisi lebih dari satubuah iterbale maka fungsi ini akan sekelsai beroperasi hingga iterable terpendek habis nilainya. Dibawah ini penejelasan tentang parameter tersebut

  1. function, parameter wajib ini diisi dengan sebuah fungsi atau lambda yang menerima nilai dari parameger iterable.
  2. iterable, parameter wajib ini diisi dengan iterable, bisa berupa list, tuple, set atau dict.
  3. iterable*, paremeter opsional ini diisi dengan iterble. Bisa lebih dari satu buah parametel opsional.

Mari coba implementasi pada sebuah studi kasus. Katakan ada data nilai mahasiswa dalam bentuk dicitonary dibawah ini. kita diarahkan untuk mengurutkan nilai mahasiswa berdasarkannilai rata-rata.

Data
students = [
dict(id=0, credits=dict(math=9, physics=6, history=7)),
dict(id=1, credits=dict(math=6, physics=7, latin=10)),
dict(id=2, credits=dict(history=8, physics=9, chemistry=10)),
dict(id=3, credits=dict(math=5, physics=5, geography=7)),
]

Code

rata_rata = sorted(map(lambda n: (sum(n["credits"].values()),n), students), reverse=True)
mengeluarkan_nilai = map(lambda n: n[1], rata_rata)

for a in mengeluarkan_nilai:
    print(a)
{'id': 2, 'credits': {'history': 8, 'physics': 9, 'chemistry': 10}}
{'id': 1, 'credits': {'math': 6, 'physics': 7, 'latin': 10}}
{'id': 0, 'credits': {'math': 9, 'physics': 6, 'history': 7}}
{'id': 3, 'credits': {'math': 5, 'physics': 5, 'geography': 7}}

Zip

Berdasarkan dokumentasi resmi python, zip adalah

Apa itu Zip

zip(*iterables)

Returns an iterator of tuples, where the i-th tuple contains the i-th element from each of the argument sequences or iterables. The iterator stops when the shortest input iterable is exhausted. With a single iterable argument, it returns an iterator of 1-tuples. With no arguments, it returns an empty iterator.

Fungsi zip() mengembalikan sebuah iterator yang berisi tuple dari setiap nilai iterable yang pada parameter. Jika iterable pada parameter-parameter yang diberikam terpendek telah habis maka operasi akan berhenti.

Dibawah ini kode yang semoga membuat anda paham tentang fungsi ini.

Code

angka_1 = [1,2,3,4,5,6]
angka_2 = [6,5,4,3,2,1]

gabungan = zip(*(angka_1,angka_2))

for a,b in gabungan:
    print(a,b,sep="|")
1|6
2|5
3|4
4|3
5|2
6|1

Untuk memahami lebih dalam tentang fungsi yang telah dicatat diatas map dan zip coba selesaikan tugas dibawah ini.

Tugas

Anda memliki nilai dibawah ini, buatlah sebuah logika menggunakan fungsi map dan zip untuk mengembalikan nilai tertinggi dari ketiga iterable ini pada setiap data. dibawahi ini adalah data yang diberikan

a = [5, 9, 2, 4, 7]
b = [3, 7, 1, 9, 2]
c = [6, 8, 0, 5, 3]
Jawaban
hasil_max = list(map(lambda n : max(n), zip(a,b,c)))
print(hasil_max)
[6, 9, 2, 9, 7]

filter

Oke, semua bersumber dari dokumentasi resmi. Sebagaimana islam bersumber dari Al-Quran dan As Sunnah sesuai dengan pemahaman para sahabat .

Apa itu Filter

filter(function, iterable)

Construct an iterator from those elements of iterable for which function returns True. iterable may be either a sequence, a container which supports iteration, or an iterator. If function is None, the identity function is assumed, that is, all elements of iterable that are false are removed.

Fungsi filter membuat sebuah interator dari elemen-elemen parameter iterable yang hanya bernilai True setelah dilakukan operasi pada sebuah fungsi atau lambda.

Katakan anda anda diberikan sekumpulan nilai student seperti contoh map diatas. Anda diperintahkan mengambil data hanya nilai yang melebihi nilai kkm sebesar 6.

Code

students = [
dict(id=0, credits=dict(math=9, physics=6, history=7)),
dict(id=1, credits=dict(math=6, physics=7, latin=10)),
dict(id=2, credits=dict(history=8, physics=9, chemistry=10)),
dict(id=3, credits=dict(math=5, physics=5, geography=7)),
]

nilai_lulus = list(filter(lambda n : min(n["credits"].values()) >= 6, students))
nilai_lulus
[{'id': 0, 'credits': {'math': 9, 'physics': 6, 'history': 7}},
{'id': 1, 'credits': {'math': 6, 'physics': 7, 'latin': 10}},
{'id': 2, 'credits': {'history': 8, 'physics': 9, 'chemistry': 10}}]

Comprehensions

Comprehension adalah notasi ringkas untuk melakukan beberapa operasi pada setiap elemen dari objek (iterable atau memeliki turunan iterator) dan atau hanya mengambil subset dari element pada obejek (seperti filter) yang sesui dengan kondisi tertentu.

Python memliki bebera tipe comprehension, seperti list comprehension, dictionary comprehensions, dan set comprehensions. Namun pada catatan ini saya hanya mencatat list comprehension. Karena dari bukuknya juga mencatumkan itu saja. Akan tetapi sytanxnya sama hanya membedakan beberapa karakter saja.

Sytanx list comprehensions
[result_value for a in squance_objects conditional_logic]

Dibawah ini cara membuat sebuah list yang berisikan kuadrat dari nilai yang diberikan. Saya akan membuat nya dengan 3 cara. pertama menggunakan perulangan, kedua memanfaat fungsi map dan ketiga menggunakan list comprehension.

Code

nilai_kuadrat = []

for a in range(10):
    nilai_kuadrat.append(a ** 2)

print (nilai_kuadrat)
Print Output
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

Kata penulis buku ini, "If you code like this, you are not python dev"

nilai_kuadrat = list(map(lambda n : n**2, range(10)))
print(nilai_kuadrat)
Print Output
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
[a**2 for a in range(10)]
Print Output
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

MasyAllah, penggunaan comprehension list sangat pendek untuk menghasilkan nilai yang sama. Sekarang mari kita coba untuk menambahkan penyaringan hanya untuk nilai yang genap dengan cara-cara diatas. Cara pertama kita akan menambahkan if conditionl dan pada cara kedua menggunakan fungsi filter(). Lalu bagaimana dengan cara yang menggunakan comprehension ? Oke lihat catatan kode dibawah ini.

Code

nilai_kuadrat = []

for a in range(10):
    if not (genap:= (a**2)) % 2: # (1)!
        nilai_kuadrat.append(genap)

print (nilai_kuadrat)
  1. not (genap:= (a**2)) % 2 ekuivalen dengan (genap:= (a**2)) % 2 == 0:
Print Output
[0, 4, 16, 36, 64]

Kode diatas memanfaatkan walrus operator

nilai_kuadrat = list(filter(lambda a : not (a % 2),map(lambda n : n**2, range(10)))) # (1)!
print(nilai_kuadrat)
  1. not (a % 2) ekuivalen dengan a %2 == 0
Print Output
[0, 4, 16, 36, 64]
[a**2 for a in range(10) if not (a**2) % 2] # (1)!
  1. not (a**2) % 2 ekuivalen dengan (a**2) % 2 == 0
Print Output
[0, 4, 16, 36, 64]

Nested comprehensions

Kode dibawah ini adalah kode yang menggunakan nested for loop

Code

kata = 'abcd'
hasil = []
for a,b in enumerate(kata):
    for c in kata[a:]:
        hasil.append((b,c))

print(hasil)  
Print Output
[('a', 'a'), ('a', 'b'), ('a', 'c'), ('a', 'd'), ('b', 'b'), ('b', 'c'), ('b', 'd'), ('c', 'c'), ('c', 'd'), ('d', 'd')]

Kode diatas juga bisa dibuat menggunakan nested list comprehensions.

Code

kata = 'abcd'
[(b,c) for a,b in enumerate(kata) for c in kata[a:]] 
Print Output
[('a', 'a'), ('a', 'b'), ('a', 'c'), ('a', 'd'), ('b', 'b'), ('b', 'c'), ('b', 'd'), ('c', 'c'), ('c', 'd'), ('d', 'd')]

Info

Infat, string juga termasuk squance didalam python

Filtering comprehensions

Sebelumnya kita telah menggunakan fungsi filtering pada comprehensions. Pada catatan ini saya hanya menuliskan kembali sebuah kode yang lebih kompleks untuk menghitung pytagoras serta hanya mengembalikan nilai rill (bilangan bulat) atas operasi tersebut.

Code

mx = 10
[ (a,b,int(c)) 
for a in range(1,mx) 
for b in range (a,mx) 
if (c:=sqrt(a**2 + b**2)).is_integer()]
Print Output
[(3, 4, 5), (6, 8, 10)]

Code

# Using for loop
from math import sqrt
mx = 10
real_pytha = []

for a in range (1,mx):
    for b in range(a,mx):
        if (hasil:= sqrt(a**2 + b**2)).is_integer():
            real_pytha.append([a,b,int(hasil)])

real_pytha
Print Output
[(3, 4, 5), (6, 8, 10)]

Dictionary Comprehensions

Sama halnya dengan list comprehensions, dictionary comprehensions hanya berbeda hasil dari comprehensions nya, yaitu menghasilkan dictionary.

Code

from string import ascii_letters # (1)!
{urut:huruf for urut, huruf in enumerate(ascii_letters[10:20])} # (2)!
  1. name ascii_letters berisikan string huruf kecil dan kapital
  2. Hanya mengambil kata dari 10 - 20, untuk meringkas hasil output
Print Output
{0: 'k',1: 'l',2: 'm',3: 'n',4: 'o',8: 's',9: 't'}

Dan, dictionary tidak bisa menampung key yang ganda

Code

sapa = 'hello farras'
{huruf:urut for urut, huruf in enumerate(sapa)}
Print Output
{'h': 0, 'e': 1, 'l': 3, 'o': 4, ' ': 5, 'f': 6, 'a': 10, 'r': 9, 's': 11}

Set Comprehensions

Set comprehensions sejenis dengan list comprehensions, hanya saja set comprehensions menggunalan {} dan Set comprehensions tidak dapat menampung nilai yang ganda.

Code

helo = 'aabbccddeefmmmnnoopqq'
{a for a in helo}  
Print Output
{'a', 'b', 'c', 'd', 'e', 'f', 'm', 'n', 'o', 'p', 'q'}

Generators

Generators adalah alat yang sangat powerfull dimana alat tersebut menjadikan pola kode menjadi lebih elegan dan efesien, Generators berdiri diatas konsep perulangan, iteration.

Ada dua tipe dari generator:

  1. Generator Function, fungsi ini sejenis dengan fungsi pada umumnya, namun jika pada fungsi umum mengembalikan nilai sekaligus dengan return value , generator function mengembalikan nilai menggunakan yield yang membuat fungsi tersebut Mengmbalikan nilai satu persatu, menunda dan melanjutkan kondisi disetiap penggunaan yield

  2. Generator Expression, sama seperti dengan list comprehensions yang mengembalikan list, generator expression mengembalikan nilai satu persatu, tidak sekaligus.

Generator Functions

Jika fungsi pada umumnya mengumpulkan nilai dan mengembalikan semua nilai tersebut sekaligus, bebeda dengan generator function yang secara otomatis berubah menjadi iterators yang mengembalikan nilai satu persatu ketika kita memanggil perintah yield.

Untuk membedakannya coba lihat duabuah kode dibawah ini;

  1. fungsi biasa dengan return statement, dan
  2. fungsi menggunakan yield statement (Generator Function).

Code

def func(n=0):
    return [a**2 for a in range(n)]

print(func(20))
Print Output
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256, 289, 324, 361]

Code

def func_gen(n=0):
    for a in range(n):
        yield a**2 #(1) !

print(list(func_gen(20)))
  1. yield tidak mengebalikan nilai sekalius akan tetapi satu persatu.
Print Output
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256, 289, 324, 361]

Hasil dari kode diatas mengembalikan nilai yang sama, yang membuat beda ialah fungsi func() adalah fungsi klasik, dimana fungsi tersebut mengumpulkan dahulu semua nilainya dan mengembalikannya secara sekaligus. Disisi lain fungsi func_gen() adalah generators, dimana setiap interpreter menyentuh perintah yield eksekusinya akan berhenti (disuspend) dan mengebalikan nilai tersebut saja. Lalu pertanyaanya mengampa mengembalikan nilai yang sama ?

Karena kita memasukan implementasi fungsi func_gen() kedalam sebuah list() yang membuat generator kehabisan baris dengan selalu memanggil fungsi next() hingga StopIteration muncul. Anda tidak melihat ini karena list bekerja demikian dibelakang. Dibawah ini pemanggilan generators menggunakan fungsi next().

Code

result_gen = func_gen(4)
print(next(result_gen))
print(result_gen.__next__()) #(1)!
print(next(result_gen))
print(next(result_gen))
print(next(result_gen)) #(2)!
  1. Bisa menggunakan fungsi next() atau menggunakan attribute __next__().
  2. Akan menghasilkan error StopIteration karena generators sudah habis.
Print Output
0
1
4
9

---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
Input In [43], in <cell line: 6>()
    4 print(next(result_gen))
    5 print(next(result_gen))
----> 6 print(next(result_gen))

StopIteration: 

Jadi apa manfaatnya menggunakan generator function dibandingkan dengan fungsi klasik jika menghasilkan keluaran nilai yang sama ? jawabannya adalah untuk menghemat waktu dan terutama tempat penyimpanan.

Info

Sangat disarankan untuk menggunakan generator function sebisa munkin untuk menghemat penyimpanan dan waktu.

Going beyond generator function

Sebagaimana yang telah saya catat di Generators, bahwa generator berdiri diatas konsep iteration. Kita juga telah melihat salah satu fungsi next() yang digunakan untuk mengembalikan element selanjutnya dari sebuah iterators.

Info

Ketika memanggil fungsi next() pada sebuah objek iterator, dibelakang layar iterpreter menjalankan fungsi spesial __next()__dari objek tersebut.

Selain fungsi next(), generator juga memliki fungsi laing yang dapat mengatur prilaku generator, yaitu fungsi send(), close(), dan throw()

Code

dir((a for a in range(1)))
Print Output
[ ... 
'close',
'gi_code',
'gi_frame',
'gi_running',
'gi_yieldfrom',
'send',
'throw'
] 

generator.next() function

Deksripsi dari dokumentasi resmi, next() function.

Info

Return the next item from the iterator. If default is given and the iterator is exhausted, it is returned instead of raising StopIteration.

Mengmbalikan item selanjutnya dari sebuah iterator dan jika iterator tersebut sudah habis, maka fungsi tersebu akan mengembalikan StopIteration.

generator.send() function

Deskripsi dari dokumentasi resmi,

Info

send 'arg' into generator, return next yielded value or raise StopIteration.

Fungsi ini berfungsi untuk mengirim sebuah sinyal dalam bentuk argumen kepada generator function yang diterima oleh statement yield, lalu mengembalikan nilai yield selanjutnya atau mengeluarkan StopIteration.

Code

def gen_func(start=0):
number = start
while True:
    yield number
    number+=1

gen = gen_func()
print(next(gen))
print(next(gen))
print(next(gen))
# Tidak akan pernah habis, forever loop
Print Output
[ ... 
0
1
2
] 

Lihat fungsi gen_func() diatas. Pamanggilan elemen pada generator tersebut tidak akan pernah habis karena tidak ada sebuah kondisi yang membuat perulangan didalam generator function tersebut berhenti. Agar tidak terjadi infinite for loop kita harus menambahkan sebuah kondisi yang dapat kita atur dari luar fungsi tersebut kapan fungsi tersebut harus berhenti.

Code

stop = False
def gen_func(start=0):
    number = start
    while True:
        if stop:
            print("Print Statement : Di stop")
            break
        yield number
        number+=1

gen = gen_func()
print(next(gen))
print(next(gen))
print(next(gen))
stop = True
print(next(gen)) #(1)! 
  1. Memunculkan StopIteration
Print Output
0
1
2
Print Statement : Di stop

---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
Input In [92], in <cell line: 16>()
    14 print(next(gen))
    15 stop = True
---> 16 print(next(gen))

StopIteration: 

Koda diatas dimodifikasi dengan membuat sebuah flag dengan name stop yang berisikan boolean value False sebagi penentuan kapan perulangan harus di break didalam generator function tersebut.

Namun cara tersebut menyebabkan bugs yang nantinya sulit untuk terdeteksi karena kode python yang lainnya dapat tidak sengaja mengubah nilai dari name stop yang dapat mempengaruhi kode pada generator function tersebut.

Cara aman untuk mengirim sinyal ke fungsi tersebut dapat menggunakan fungsi send() pada generator.

Code

def gen_func(start=0):
    number = start
    while True:
        status = yield number # 2
        if status: # 3
            print("Print Statement : Di stop")
            break # 4
        number+=1

gen = gen_func()
print(next(gen))
print(next(gen))
print(next(gen))
gen.send(True) # 1
print(next(gen)) #(1)!
  1. Memunculkan StopIteration
Print Output
0
1
2
Print Statement : Di stop

---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
Input In [92], in <cell line: 16>()
    14 print(next(gen))
    15 stop = True
---> 16 print(next(gen))

StopIteration: 

Dibawah ini adalah penjelasan bagaimana fungsi send() memberikan sinyal ke generator function terhadap kode diatas.

  1. Pada baris ke 14 mengirim sinyal berupa nilai True ke generator function gen_func().
  2. Sinyal ditangkap oleh statement yield dan di assign kepada name status.
  3. Kondisi pada baris 5 memenuhi syarat, sehingga body pada conditional if tersebut akan di jalankan.
  4. Memberhentikan iterator while.

Untuk lebih memahami, lihatlah kode dibawah ini. Kode ini didapat dari sumber catatan ini pada bagian tetang generator.

Code

def gen_func(start=0):
    print("Memulai generator")
    print("-"*30)
    number = start
    while True:
        signal = yield number #(1)!
        print(f'Tipe Sinyal : {type(signal)} | Nilai Sinyal : {signal}')
        if signal == 'Q':
            print("Print Statement : Di stop")
            print("-"*30)
            break
        number+=1

gen = gen_func()
print(next(gen))
print(next(gen))
print(gen.send(1))
print(gen.send(True))
print(gen.send([]))
print(next(gen))
print(gen.send('Q'))
  1. Statement yield menunda pengembalian nilai hinggai pemanggilan fungsi next()
Print Output
Memulai generator
------------------------------
0
Tipe Sinyal : <class 'NoneType'> | Nilai Sinyal : None
1
Tipe Sinyal : <class 'int'> | Nilai Sinyal : 1
2
Tipe Sinyal : <class 'bool'> | Nilai Sinyal : True
3
Tipe Sinyal : <class 'list'> | Nilai Sinyal : []
4
Tipe Sinyal : <class 'NoneType'> | Nilai Sinyal : None
5
Tipe Sinyal : <class 'str'> | Nilai Sinyal : Q
Print Statement : Di stop
------------------------------

---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
Input In [117], in <cell line: 21>()
    19 print(gen.send([]))
    20 print(next(gen))
---> 21 print(gen.send('Q'))

StopIteration: 

Ingat

Statement yield menunda (suspend) pengembalian nilai hingga pemanggilan fungsi next()

generator.throw() function

Deskripsi dari dokumentasi resmi,

Info

raise exception in generator, return next yielded value or raise StopIteration.

Fungsi throw() akan mengeluarkan sebuah exception dalam generator function. Lihat kode dibawah ini contoh pemanfaatan fungsi throw().

Code

class ExceptionBuatan(Exception):
    """Exception buatan"""

def gen_func(n=1):
    number = n
    try:
        while True:
            value = yield f'Ini angka {n} the value'
            n+=1
    except ExceptionBuatan:
        yield "Stop karena keinginan" #(1)! masih dapat yielded value
    except StopIteration:
        yield "Generator habis"

gen = gen_func()
print(next(gen))
print(next(gen))
print(next(gen))
print(gen.throw(ExceptionBuatan))
  1. Pada block Exception tetap dapat me-return value dengan statement yield
Print Output
Ini angka 1 the value
Ini angka 2 the value
Ini angka 3 the value
Stop karena keinginan

Hmmm, saya menemukan sebuah jawabn dari pertanyaan pada website Stack overflow tentang implementasi yang cocok dari fungsi .throw(), look at this additional note.

generator.close() function

Fungsi .close() akan memunculkan GeneratorExit exception pada generator function.

Code

def gen_func(n=1):
    number = n
    try:
        while True:
            value = yield f'Ini angka {n} the value'
            n+=1
    except ExceptionBuatan:
        yield "Stop karena keinginan"
    except StopIteration:
        yield "Generator habis"
    except GeneratorExit:
        print ("Generator di close")

gen = gen_func()
print(next(gen))
gen.close()
Print Output
Ini angka 1 the value
Generator di close

Statement yield from expression

Python menyediakan statement yield from pada pola kode yang lebih kompleks. Dengan statement tersebut anda dapat mengembalikan nilai pada sub iterator atau generator lainnya.

Katakan kita ingin membuat dua buah generator function, pertama gen_1 yang berisikan kode memunculkan akar pangkat dari bilangan berurut yang diberikan pada argumen fungsi tersebut, dan kedua pring_def_gen() yang mengembalikan costume print dari fungsi pertama.

Code

def gen_1 (n = 1):
    for a in range(n):
        yield a**2

def print_def_gen():
    print("Start")
    yield from gen_1(3)
    print("Akhir")

for a in print_def_gen():
    print(a)
Print Output
Start
0
1
4
Akhir 

Untuk catatan generator expression setelah bagian catatan ini.

Code

def print_def_gen(n = 1):
    print("Start")
    yield from (a**2 for a in range(n))
    print("Akhir")

for a in print_def_gen(3):
    print(a)
Print Output
Start
0
1
4
Akhir 

Generator Expression

Syntax Generator Expression mirip dengan list comprehensions. Jika list comprehensions menggunakan open bracket, generator expression menggunakan round bracket. Dan yang perlu anda ingat adalah, expression ini mengembalikan generator object yang mana setiap elemenya hanya dapat dipanggil sekali.

Code

gen_exp = (a**2 for a in range(4))
print (gen_exp)
print(list(gen_exp)) #(1)!
print(list(gen_exp)) #(2)!
  1. Setiap item pada generator akan dipanggil serta ditampung pada list hingga setiap element pada generator tersebut habis.

  2. List tidak akan menampung apa-apa,kerena generator sudah habis dipakai pada pemanggilan diatas.

Print Output
<generator object <genexpr> at 0x7f77e2b21ac0>
[0, 1, 4, 9]
[]

Dibawah ini mari kita perbandingan penggunaan fungsi map() dalam membuat sebuah algoritma dengan menggunakan generator expression.

Code

def add(*n): #(1)! ingat, variable positional parameter
    return sum(n)

hasil = map(add, range(100), range(1,101))
print(list(hasil))
Print Output
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63, 65, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 87, 89, 91, 93, 95, 97, 99, 101, 103, 105, 107, 109, 111, 113, 115, 117, 119, 121, 123, 125, 127, 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149, 151, 153, 155, 157, 159, 161, 163, 165, 167, 169, 171, 173, 175, 177, 179, 181, 183, 185, 187, 189, 191, 193, 195, 197, 199]

Code

def add(*n): #(1)! 
    return sum(n)

hasil = (add(*a) #(2)! ingat, iterable unpacking
        for a in zip(range(100),range(1,101)))
print(list(hasil))
  1. Ingat, variable positional parameter
  2. Ingat, iterable unpacking
Print Output
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63, 65, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 87, 89, 91, 93, 95, 97, 99, 101, 103, 105, 107, 109, 111, 113, 115, 117, 119, 121, 123, 125, 127, 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149, 151, 153, 155, 157, 159, 161, 163, 165, 167, 169, 171, 173, 175, 177, 179, 181, 183, 185, 187, 189, 191, 193, 195, 197, 199] 

Sekarang, mari kita lihat perbedaan penggunaan pemecahan masalah menggunakan filter dan generator expression. Katakan, buat sebuah return value yang menampilkan \((n,n^3)^1,(n,n^3)^2,(n,n^3)^3,...,(n,n^3)^n\) untuk urutan jumlah n dalam bilangan berurut.

Code

hasil = map(lambda n : (n, n**3), filter(lambda n : not n%3 or not n%5, range(10)))
print(list(hasil))
Print Output
[(0, 0), (3, 27), (5, 125), (6, 216), (9, 729)]

Code

gen_exp = ((n,n**3) for n in range(10) if not n%3 or not n%5)
print(list(gen_exp))
Print Output
[(0, 0), (3, 27), (5, 125), (6, 216), (9, 729)]

Selain meninkatkan penulisan kode yang lebih sedikit dan lebih elegan, penggunaan dari generator expression juga meningkatkan kecepatan waktu proses. Dibawah ini perbandingan penggunaan list comprehension dengan generatro expression

Code

import time as tm

beg = tm.time()
hasil = sum([a**2 for a in range (10**8)])
print(f'Waktu yang dibutuhkan {(tm.time()-beg):.4f} detik')
Print Output
Waktu yang dibutuhkan 31.9118 detik

Code

import time as tm

beg = tm.time()
hasil = sum(a**2 for a in range (10**8))
print(f'Waktu yang dibutuhkan {(tm.time()-beg):.4f} detik')
Print Output
Waktu yang dibutuhkan 31.7187 detik

Name Localization

Pada catatan tentang namespace telah dipelajari tentang LEGB (Local, Enclosing, Global, Built-in). Pada python3, interpreter akan me-localize variable atau names pada bentuk atau bentuk dari list comprehensions, dictionary comprehensions, set comprehensions dan generators. Namun tidak pada for loop,

Code

a = 100

hasil = sum(a for a in range(10))
print(f'Nilai a setelah generator expressions : {a}')

hasil2 = sum([a for a in range(10)])
print(f'Nilai a setelah list comprehensions : {a}')

hasil2 = sum({a for a in range(10)})
print(f'Nilai a setelah set comprehensions : {a}')


lst = []
for a in range(10):
    lst.append(a) #(1)!

hasil3 = sum(lst)
print(f'Nilai a setelah for loop : {a}')
  1. Merubah nilai gelobal name a
Print Output
Nilai a setelah generator expressions : 100
Nilai a setelah list comprehensions : 100
Nilai a setelah set comprehensions : 100
Nilai a setelah for loop : 9

Liha pada baris ke-15. Nilai global names a ditiban dengan assign value pada for loop.

Latihan

Coba buatkan sebuah kode untuk membuat nomor urut fibonacci.

Penjelasan algo fibonacci

Code

lst = []
a = 0

while a <= 10:
    if a in (0,1):
        lst.append(a)
    else:
        lst.append(lst[a-1]+lst[a-2])
    a+=1

print(lst)

Code

# More elegan
def febon_v2 (n=1):

    lst = [0]
    next_append = 1
    while len(lst) < n:
        lst.append(next_append)
        next_append = sum(lst[-2:])

    return lst



print(febon_v2(20))

Code

# Menggunakan generator function
def fibon_gen(n=1):
    count = 0
    cur_value = 0
    next_append = 1
    while count < n:
        yield cur_value
        cur_value,  next_append= next_append,next_append+cur_value
        count+=1
gen_fibon = fibon_gen(7)