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()
, danfilter()
- 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
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
- function, parameter wajib ini diisi dengan sebuah fungsi atau lambda yang menerima nilai dari parameger iterable.
- iterable, parameter wajib ini diisi dengan iterable, bisa berupa list, tuple, set atau dict.
- 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.
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
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
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
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
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.
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
Kata penulis buku ini, "If you code like this, you are not python dev"
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)
not (genap:= (a**2)) % 2
ekuivalen dengan(genap:= (a**2)) % 2 == 0:
Kode diatas memanfaatkan walrus operator
Nested comprehensions
Kode dibawah ini adalah kode yang menggunakan nested for loop
Code
Kode diatas juga bisa dibuat menggunakan nested list comprehensions.
Code
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.
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)!
- name
ascii_letters
berisikan string huruf kecil dan kapital - Hanya mengambil kata dari 10 - 20, untuk meringkas hasil output
Dan, dictionary tidak bisa menampung key yang ganda
Code
Set Comprehensions
Set comprehensions sejenis dengan list comprehensions, hanya saja set comprehensions menggunalan {}
dan Set comprehensions tidak dapat menampung nilai yang ganda.
Code
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:
-
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
-
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;
- fungsi biasa dengan return statement, dan
- fungsi menggunakan yield statement (Generator Function).
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)!
- Bisa menggunakan fungsi
next()
atau menggunakan attribute__next__()
. - Akan menghasilkan error StopIteration karena generators sudah habis.
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
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
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
- Memunculkan
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
- Memunculkan
StopIteration
Dibawah ini adalah penjelasan bagaimana fungsi send()
memberikan sinyal ke generator function terhadap kode diatas.
- Pada baris ke 14 mengirim sinyal berupa nilai
True
ke generator functiongen_func()
. - Sinyal ditangkap oleh statement
yield
dan di assign kepada namestatus
. - Kondisi pada baris 5 memenuhi syarat, sehingga body pada conditional if tersebut akan di jalankan.
- 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'))
- Statement
yield
menunda pengembalian nilai hinggai pemanggilan fungsi next()
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))
- Pada block
Exception
tetap dapat me-return value dengan statementyield
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
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.
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)!
-
Setiap item pada generator akan dipanggil serta ditampung pada list hingga setiap element pada generator tersebut habis.
-
List tidak akan menampung apa-apa,kerena generator sudah habis dipakai pada pemanggilan diatas.
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))
[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))
- Ingat, variable positional parameter
- Ingat, iterable unpacking
[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.
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
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
- Merubah nilai gelobal name a
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.