Decorator

Pada artikel ini, kita akan mempelajari tentang decorator, cara membuat decorator, dan kapan kita sebaiknya menggunakan decorator.

 

Tabel Konten


Pengertian Decorator

Python memiliki sebuah fitur menarik bernama decorator yang berfungsi menambah fungsionalitas pada kode program.

Hal ini disebut juga metaprogramming karena ada bagian dari program yang mencoba untuk memodifikasi bagian lainnya pada saat eksekusi.


Syarat Sebelum Mempelajari Decorator

Sebelum dapat memahami tentang decorator, kita harus terlebih dahulu memahami dasar – dasar Python.

Kita harus paham terlebih dahulu bahwa semua yang ada di Python adalah objek (termasuk kelas). Nama pengenal, seperti variabel yang kita deklarasikan merujuk kepada objek tersebut. Begitu juga dengan fungsi. Fungsi adalah termasuk objek juga. Satu objek bisa memiliki banyak pengenal (identifier) yang merujuk kepadanya.

Perhatikan contoh berikut

  def first(msg): print(msg) first("Hello") second = first second("Hello")

Pada saat kode di atas dijalankan, kedua fungsi first dan second menampilkan output yang sama. Di sini, variabel first dan second merujuk pada objek fungsi yang sama.

Sekarang mari kita tinjau hal yang lain. Sebuah fungsi bisa dijadikan sebagai argumen dari fungsi yang lain.

Bila Anda sudah pernah menggunakan fungsi seperti map, filter, dan reduce di Python, maka Anda sudah tahu tentang hal ini.

Fungsi yang menjadikan fungsi lain sebagai argumen disebut juga fungsi dengan orde yang lebih tinggi. Berikut adalah contohnya:

def inc(x): return x + 1 def dec(x): return x - 1 def operate(func, x): result = func(x) return result

Kita bisa memanggil fungsi tersebut seperti berikut:

>>> operate(inc, 3)
4
>>> operate(dec, 3)
2

Lebih lanjut lagi, sebuah fungsi bisa mengembalikan fungsi lain.

def is_called(): def is_returned(): print("Hello") return is_returned new = is_called() #Outputs "Hello" new()

Pada contoh tersebut, is_returned() adalah fungsi bersarang yang didefinisikan dan dikembalikan tiap kali fungsi is_called() dipanggil.

Selain memahami tentang hal di atas, kita juga sudah harus paham tentang python closure


Kembali ke Decorator

Sebuah decorator mengambil fungsi sebagai argumennya, menambahkan beberapa hal, dan kemudian mengembalikannya.

def make_pretty(func): def inner(): print("I got decorated") func() return inner def ordinary(): print("I am ordinary")

Bila kode di atas kita jalankan pada mode interaktif, maka hasilnya adalah seperti berikut:

>>> ordinary()
I am ordinary

>>> # Mari kita buat decorator dari fungsi ordinary
>>> pretty = make_pretty(ordinary)
>>> pretty()
I got decorated
I am ordinary

Pada contoh di atas, make_pretty() adalah sebuah decorator.

Pada baris perintah pretty = make_pretty(ordinary), fungsi ordinary didekorasi dan fungsi kembaliannya diberi nama pretty.

Kita bisa lihat bahwa fungsi decorator menambahkan beberapa fungsionalitas ke fungsi asli. Hal ini mirip dengan pengemasan kado. Decorator bertindak sebagai bungkusnya. Objek yang didekorasi (isi kado) tidak berubah. Akan tetapi, ketika dibungkus, akan terlihat lebih bagus (karena didekorasi).

Umumnya, kita mendekorasi fungsi dan menyimpannya ke variabel seperti berikut:

ordinary = make_pretty(ordinary)

Python memiliki sintaks yang lebih sederhana untuk penulisan fungsi di atas yaitu dengan menggunakan tanda  @ diikuti oleh nama fungsi decorator. Kita menempatkannya di atas fungsi yang akan didekorasi. Maka contoh berikut ini:

@make_pretty
def ordinary():
    print("I am ordinary")

adalah sama dengan yang berikut:

def ordinary():
    print("I am ordinary")
ordinary = make_pretty(ordinary)

Cara yang menggunakan tanda @ di atas adalah sintaks yang lebih elegan untuk mengimplementasikan decorator.


Dekorasi Fungsi Dengan Parameter

Decorator di atas sangat sederhana dan hanya berlaku untuk fungsi yang tidak memiliki parameter. Bagaimana jika fungsi yang akan didekorasi memiliki argumen seperti berikut?

def divide(a, b):
    return a/b

Fungsi tersebut memiliki 2 parameter, a dan b. Bila kita melewatkan nilai 0 ke b, maka akan terjadi error ZeroDivisionError.

>>> divide(2, 5)
0.4
>>> divide(2, 0)
Traceback (most recent call last):
...
ZeroDivisionError: division by zero

Sekarang kita akan membuat decorator untuk mengecek penyebab error ini.

  def smart_divide(func): def inner(a,b): print("Saya akan membagi",a,"dan",b) if b == 0: print("Whoops! tidak bisa membagi dengan 0") return return func(a,b) return inner @smart_divide def divide(a,b): return a/b  

Kode yang menggunakan decorator ini akan mengembalikan None jika terjadi error.

>>> divide(2, 5)
Saya akan membagi 2 dan 5
0.4

>>> divide(2, 0)
Saya akan membagi 2 dan 0
Whoops! tidak bisa membagi dengan 0

Dengan cara tersebut kita bisa mendekorasi fungsi yang memiliki parameter.

Bila kita perhatikan dengan baik, kita akan melihat kalau semua parameter dari fungsi yang di dalam decorator akan menjadi parameter dari fungsi yang didekorasi. Dengan itu, kita bisa membuat decorator yang lebih umum yang dapat bekerja dengan berapapun jumlah parameternya.

Di Python, hal ini dapat dilakukan dengan sintaks function(*args, **kwargs) di mana args adalah tuple dari argumen – argumen berdasarkan posisi, dan kwargs adalah dictionary dari argumen – argumen ber-keyword.

Berikut ini adalah contoh fungsi inner() yang memiliki sembarang parameter didekorasi oleh fungsi works_for_all().

def works_for_all(func):
    def inner(*args, **kwargs):
        print("Saya bisa mendekorasi fungsi apa saja")
        return func(*args, **kwargs)
    return inner()

Menggabungkan Decorator di Python

Beberapa decorator bisa dihubungkan atau digabungkan. Sebuah fungsi bisa didekorasi beberapa kali dengan satu atau beberapa fungsi decorator. Kita meletakkan decorator di atas fungsi yang akan didekorasi. Untuk jelasnya perhatikan contoh berikut:

  def star(func): def inner(*args, **kwargs): print("*" * 30) func(*args, **kwargs) print("*" * 30) return inner def percent(func): def inner(*args, **kwargs): print("%" * 30) func(*args, **kwargs) print("%" * 30) return inner @star @percent def printer(msg): print(msg) printer("Hello")

Outputnya akan tampak seperti berikut:

******************************
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Hello
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
******************************

 

Kode decorator di atas yaitu:

@star
@percent
def printer(msg):
    print(msg)

adalah sama dengan:

def printer(msg):
    print(msg)
printer = star(percent(printer))

 

Urutan dari decorator perlu diperhatikan. Bila kita mengubah urutannya menjadi:

@percent
@star
def printer(msg):
    print(msg)

maka hasilnya akan menjadi:

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
******************************
Hello
******************************
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%