CLOSURE, DECORATOR, GENERATOR
Introduction, playing with anagram
Hitung keberadaan semua huruf dalam kata dan simpulkan apakah dua kata yang diberikan berupa anagram atau tidak. Contoh "setec astronomy" adalah anagram dari "too many secrets".
# anagram
def is_anagram(word1, word2):
count1, count2 = {}, {}
space_key = ' '
for word, count in ((word1, count1), (word2, count2)):
for char in word:
count[char] = count.get(char, 0) + 1
if space_key in count:
count.pop(space_key) # ignore spaces
if count1 == count2:
return True
return False
print(is_anagram('setec astronomy', 'too many secrets')) # True
Kita akan mengembangkan contoh ini untuk menjelaskan fitur fitur penting Python.
CLOSURE
# closure
def char_occurence():
counter = {}
def count_it(word):
for c in word:
counter[c] = counter.get(c, 0) + 1
return counter
return count_it
fcnt = char_occurence()
print(fcnt('setec'))
func_count = char_occurence()
for word in "CLOSURE MEMBUNGKUS VARIABEL GLOBAL".split(" "):
result = func_count(word)
print(dict(sorted(result.items(), key=lambda item: item[1], reverse=True)))
{'s': 1, 'e': 2, 't': 1, 'c': 1}
{'C': 1, 'L': 1, 'O': 1, 'S': 1, 'U': 1, 'R': 1, 'E': 1}
{'U': 3, 'S': 2, 'E': 2, 'M': 2, 'C': 1, 'L': 1, 'O': 1, 'R': 1, 'B': 1, 'N': 1, 'G': 1, 'K': 1}
{'U': 3, 'E': 3, 'L': 2, 'S': 2, 'R': 2, 'M': 2, 'B': 2, 'A': 2, 'C': 1, 'O': 1, 'N': 1, 'G': 1, 'K': 1, 'V': 1, 'I': 1}
{'L': 4, 'U': 3, 'E': 3, 'B': 3, 'A': 3, 'O': 2, 'S': 2, 'R': 2, 'M': 2, 'G': 2, 'C': 1, 'N': 1, 'K': 1, 'V': 1, 'I': 1}
Mari kita amati script di atas.
Fungsi char_occurence memiliki dua objek yaitu objek dictionary bernama counter dan objek fungsi bernama count_it; jika dipanggil dictionary counter dikosongkan, dan fungsi count_it berisi referens objek counter, dikembalikan ke pemanggil. Objek fungsi itu adalah closure dan ia membawa sertanya dictionary counter yang disebut free-variable.
Free-variable memiliki umur scope global, tetapi tidak didefinisikan dalam scope global, dan diakses melalui fungsi dimana ia didefinisikan. Tanpa mekanisme closure maka variabel counter harus didefinisikan pada scope global dan dalam fungsi count_it harus terdapat statement global counter, supaya counter dapat diupdate setiap kali fungsi count_it(arg) dipanggil. Perhatikan juga bahwa sebagai fungsi di dalam fungsi, objek fungsi count_it tidak dapat dipanggil langsung, terhalang fungsi pembungkusnya, makanya ia disebut closure. Biasanya closure memiliki free variable(s) yang berasal dari environment ketika closure didefinisikan.
Informasi lebih lanjut bisa kita lihat dengan menjalankan perintah sbb:
for f in (fcnt, func_count):
print(f.__name__, (fv := f.__code__.co_freevars), id(fv))
Setiap kita memanggil fungsi wrapper/pembungkus, free-variable diinisialisasi ulang, lalu bila kita memanggil closure berulang dengan nama yang berbeda, free-variable tetap objek yang sama, statis, terlihat dari alamatnya id(fv) yang sama.
DECORATOR
# decorator
def mydeco(fn):
counter = {}
def closure(line):
for k, v in fn(line).items():
counter[k] = counter.get(k, 0) + v
return counter
return closure
@mydeco
def ch_count(line):
dy = {}
for word in line.split(' '):
for ch in word:
dy[ch] = dy.get(ch, 0) + 1
return dy
# @mydeco is syntactic sugar of saying ch_count = mydeco(ch_count)
print(ch_count('anagram closure decorator'))
{'a': 4, 'n': 1, 'g': 1, 'r': 4, 'm': 1, 'c': 2, 'l': 1, 'o': 3, 's': 1, 'u': 1, 'e': 2, 'd': 1, 't': 1}
Decorator adalah suatu fungsi yang membungkus fungsi lain, lalu diganti namanya dengan nama semula, func = deco(func), artinya suatu fungsi func setelah didekorasi dengan fungsi lain, namanya tetap sebagai fungsi func.
Manfaat decorator adalah mendapatkan free variables, dan menambahkan suatu fitur lain tanpa mengedit fungsi aslinya.
Berikut contoh memperbaiki kinerja fungsi penghitung deret Fibonacci dengan menambahkan fungsi pembungkus memoize.
# Naive Fibonacci dengan kinerja yang parah
def fibo(n):
assert n>=0, 'not for negative argument'
if n in (0,1):
return n
return fibo(n-1) + fibo(n-2)
print(fibo(10))
print(fibo(34)) # this one will take a moment, please wait.
Rekursi fibonacci ini benar-benar parah karena terdapat dua kali rekursi yang menghitung sendiri sendiri lalu ada operasi penjumlahan di luar rekursi. Pada skrip di atas, kalkulasi fibo(34) memerlukan waktu beberapa detik karena melakukan iterasi sampai jutaan kali untuk perhitungan yang sama berulang-ulang.
Kita dapat memperbaiki kinerja waktu eksekusinya, dengan dekorasi kecil saja, tanpa mengubah fungsi awalnya.
# Naive Fibonacci didekorasi dengan memoize
def memoize(fn):
memo = {}
def closure(n):
if n not in memo:
memo[n] = fn(n)
return memo[n]
return closure
@memoize
def fib(n):
assert n >= 0, 'not for negative argument'
if n in (0, 1):
return n
return fib(n - 1) + fib(n - 2)
print(fib(10)) # this one is fast
print(fib(34)) # this one is fast
print(fib(340)) # this one is fast
Bila di-run maka akan terlihat peningkatan kinerja yang awesome, karena kita menambahkan memo supaya tidak menghitung ulang perhitungan yang sudah pernah dilakukan. Penambahan itu dilakukan dalam fungsi lain yang membungkus fungsi awal. Jadi secara umum kita tidak perlu mengedit suatu fungsi yang sudah dibuat, dan dipakai, jika ingin menambah sesuatu atau memperbaiki kinerjanya. Melalui decorator, cara pemanggilan fungsi itu tidak berubah sama sekali, isi fungsi tidak berubah, hanya sedikit perubahan sebelum definisinya, ada tambahan @decorator. Sederhana bukan?
GENERATOR, YIELD
def fib():
""" fibonacci generator,
with bottom up algorithm in simple variables
"""
a, b, i = 1, 1, 1 # initial values
while True: # generator can iterates over an infinite loop
yield i, a # generator yields one set of values, at a time, then PAUSE
a, b, i = b, a + b, i + 1 # a mere tuple assignment
for idx, result in fib(): # iterate over the generator
if (nnn := len(str(result))) > 1000: # jika hasil sudah memenuhi kriteria
break # STOP it, now!
print(f'fibonacci({idx}) = {result}') # tidak ada scope baru untuk blok for..loop
print(f'length: {nnn} digits') # variabel dalam blok bisa diakses dari luar blok
fibonacci(4787) = 11867216745258291596767088485966669273798582100095758927648586619975930687764095025968215177396570693265703962438125699711941059562545194266075961811883693134762216371218311196004424123489176045121333888565534924242378605373120526670329845322631737678903926970677861161240351447136066048164999599442542656514905088616976279305745609791746515632977790194938965236778055329967326038544356209745856855159058933476416258769264398373862584107011986781891656652294354303384242672408623790331963965457196174228574314820977014549061641307451101774166736940218594168337251710513138183086237827524393177246011800953414994670315197696419455768988692973700193372678236023166645886460311356376355559165284374295661676047742503016358708348137445254264644759334748027290043966390891843744407845769620260120918661264249498568399416752809338209739872047617689422485537053988895817801983866648336679027270843804302586168051835624516823216354234081479331553304809262608491851078404280454207286577699580222132259241827433
length: 1001 digits
Generator sangat hemat memori karena tidak membuat list berisi semua values, tetapi hanya mengembalikan satu set values kemudian pause menunggu permintaan iterasi berikutnya. Dalam contoh skrip di atas generator dimainkan dalam for-ever loop tanpa masalah.
Baik, sampai disini dulu pembahasan kita kali ini, kisanak.
Salam,
.
Comments
Post a Comment