November 22, 2020

1626 words 8 mins read

Python Decorator

Python Decorator

Decorator merupakan komponen penting dalam Python yang mendukung fleksibilitas dalam manipulasi fungsi. Per definisi, decorator adalah fungsi yang melakukan operasi terhadap fungsi lain dan memodifikasi perilakunya tanpa harus mengubah secara eksplisit.

Decorator bekerja menggunakan prinsip metaprogramming karena ia akan memodifikasi bagian program lainnya pada saat eksekusi. Secara konsep, decorator menggunakan metode inner function dan Python closure. Sebelum membahas lebih lanjut tentang decorator, berikut ini akan dibahas beberapa istilah dan komponen Python yang berkaitan.

Fungsi

Dalam Python, fungsi digunakan untuk mengatur alur/logika dalam program. Fungsi dibuat untuk melakukan aksi tertentu dan dapat dipanggil kapanpun fungsi tersebut dibutuhkan. Fungsi pada Python didefinisikan dengan menggunakan kata kunci def kemudian diikuti dengan nama fungsinya seperti pada contoh berikut ini:

def nama_fungsi():
    print("Ini adalah Fungsi")

Fungsi dapat dipanggil dengan menuliskan nama fungsi berikut paramaternya. Untuk contoh di atas, karena didefinisikan tanpa parameter maka fungsi tersebut dipanggil tanpa menggunakan argumen: nama_fungsi().

Pemanggilan nama fungsi diikuti dengan tanda kurung (parentheses) akan mengeksekusi fungsi tersebut. Pada contoh di atas, fungsi akan mencetak Ini adalah Fungsi.

Fungsi dengan Parameter

Fungsi dapat menerima parameter berupa nilai yang yang akan menentukan keluaran fungsi tersebut. Nilai parameter adalah input dari luar yang akan diproses secara internal. Nilai parameter saat pemanggilan fungsi ini disebut juga sebagai argumen.

Terdapat beberapa jenis argumen fungsi yang bisa digunakan yaitu: argumen wajib (required argument), argumen default dan argumen dengan jumlah bervariasi.

Fungsi dengan argumen wajib mengharuskan pengguna untuk memberikan argumen dengan jumlah sesuai dengan yang telah ditentukan (pada parameter fungsi). Pada contoh berikut terdapat sebuah parameter dan disebut nama. Parameter nama bisa menerima argumen berupa string, integer, fungsi atau tipe data lain.

def halo_nama(nama):
    print(nama)

halo_nama('nyayu')

Pengguna juga dapat memasukkan keyword pada argumen:

def halo_nama(nama):
    print(nama)

halo_nama(nama='nyayu')

Argumen default berguna untuk memberikan nilai default pada parameter. Nilai default ini akan dipakai apabila fungsi dieksekusi tanpa menggunakan argumen.

Apabila fungsi dipanggil menggunakan argumen, maka fungsi akan menghasilkan output sesuai dengan argumen yang dimasukkan pengguna.

def halo_nama(nama='Nyayu'):
    print(nama)

halo_nama()
halo_nama('Nias')

Argumen dengan jumlah bervariasi digunakan apabila jumlah parameter tidak bisa ditentukan secara pasti. Untuk ini digunakan parameter dengan keyword *args dan **kwargs, sehingga parameter menjadi bersifat dinamis. Sintaks *args digunakan untuk melewatkan sembarang jumlah argumen ke fungsi. Dengan simbol * di depan parameter akan menyebabkan fungsi menerima sejumlah argumen yang akan dikonsolidasikan ke dalam sebuah list. Sedangkan sintaks **kwargs digunakan untuk melewatkan sembarang jumlah argumen dengan menggunakan keyword (pasangan key-value - seperti pada dictionary) ke dalam fungsi. Simbol ** berfungsi untuk mengubah semua argumen keyword menjadi dictionary. Penamaan sintaks *args dan *kwargs bukan merupakan sesuatu yang baku tetapi umum digunakan karena args merepresentasikan arguments dan kwargs berarti keyword arguments. Pengguna dapat menggunakan penamaan lain seperti : *nama, **knama, **kbuku dan sebagainya.

def halo_nama(*args,**kwargs):
    print([i for i in args])
    print({k:v for k,v in kwargs.items()})
    
halo_nama('a',1,5,'bc',a=2,b='abc')

Fungsi sebagai First-Class Object

Yang dimaksud fungsi sebagai first-class object adalah fungsi dapat disimpan dalam struktur data, dijadikan argumen atau dijadikan return value dari fungsi lain. Karakteristik ini penting, karena decorator akan menggunakan fungsi sebagai argumennya, menambahkan beberapa hal terhadap fungsi tersebut dan selanjutnya dikembalikan menjadi fungsi yang telah dimodifikasi. Berikut ini contoh dua buah fungsi untuk menjumlahkan dua buah bilangan:

def jumlah(a,b):
    return a+b

def Jumlah10dan2(fungsijumlah):
    return fungsijumlah(10,2)

Jumlah10dan2(jumlah)

Fungsi pertama merupakan fungsi yang menerima dua buah bilangan dan menjumlahkan bilangan tersebut. Sedangkan fungsi kedua Jumlah10dan2, merupakan fungsi yang secara spesifik menjumlahkan angka 10 dan 2. Berbeda dengan fungsi pertama yang menerima argumen dalam bentuk bilangan, fungsi Jumlah10dan2 menerima argumen dalam bentuk fungsi. Artinya, fungsi Jumlah10dan2 memanggil fungsi jumlah untuk mengoperasikan penjumlahan angka 10 dan 2.

Inner Function

Secara definisi, inner function atau juga dikenal dengan nested function merupakan fungsi yang didefinisikan di dalam fungsi. Pada dasarnya, sebuah fungsi dibuat sebagai inner function untuk memisahkan/membatasi dari semua yang terjadi di luar fungsi induk (outer function). Artinya, inner function hanya bisa mengakses variabel dari lingkup fungsi induk, dan tidak dapat berinteraksi dengan variabel di luar fungsi induk.

def OuterFunc():
    print("Ini adalah Outer Function")

    def InnerFunc():
        print("Ini adalah Inner Function")

    return InnerFunc()

OuterFunc()

Walaupun inner function didefinisikan seperti fungsi pada umumnya, fungsi ini tidak bisa dipanggil secara langsung. Hal ini karena fungsi tersebut tidak terdefinisi secara global. Inner Function merupakan object lokal yang hanya akan terdefinisi jika fungsi induknya dipanggil.

Fungsi yang Mengembalikan Fungsi

Selain dapat mengembalikan nilai, fungsi juga dapat suatu mengembalikan (object) fungsi. Untuk lebih jelasnya perhatikan contoh berikut. Pada fungsi berikut, fungsi menu() menerima argumen berupa huruf. Apabila pengguna memasukkan huruf a maka fungsi akan mengembalikan fungsi pizza. Proses ini merupakan proses pengembalian fungsi dari fungsi.

def menu(huruf):
    def pizza():
        return "You ordered Pizza"
    def bakso():
        return "You ordered Bakso"

    if huruf=='a':
       return pizza
    else:
       return bakso

Pada contoh di atas, fungsi pizza dan bakso dikembalikan tanpa tanda kurung (parentheses). Dengan cara ini, pengguna sebenarnya tidak memanggil fungsi melainkan memanggil referensi objek fungsi tersebut:

menu('a')
# referensi ke object fungsi pizza

menu('b')
# referensi ke object fungsi bakso

Untuk mengembalikan nilai berupa “You ordered pizza/bakso”, fungsi harus dipanggil menggunakan parentheses. Oleh karena itu untuk mengembalikan nilai dari fungsi tersebut, pengguna dapat membuat variabel baru yang berisi referensi dari fungsi seperti berikut:

pizza = menu('a')
bakso = menu('b')

pizza()
bakso()

Pada contoh di atas, variabel pizza sudah menyimpan referensi dari fungsi pizza, sehingga variabel ini sekarang telah menjadi fungsi dan dapat dipanggil menggunakan parentheses.

Python Closure

Konsep Python Closure sangat berhubungan dengan inner function dan variabel non-lokal. Variabel non-lokal adalah variabel yang tidak didefinisikan di dalam lingkup lokal. Variabel ini digunakan di dalam fungsi bersarang. Berikut ini contoh penggunaan variabel non-lokal dalam fungsi bernama sapa().

def sapa(pesan):
    def tampilkan():
        print(pesan)

    tampilkan()

sapa('Halo Nyayu')

Pada contoh di atas, fungsi tampilkan() dapat mengakses variabel pesan yang terdapat yang terdapat pada fungsi induknya (di luar fungsinya sendiri). Dalam hal ini, fungsi tampilkan( ) mengakses variabel non-lokal. Sehingga, output fungsi sapa('Halo Nyayu') dapat ditampilkan pada layar. Untuk bisa menjalankan fungsi sapa(), pengguna harus memberikan argumen wajib pada fungsi tersebut seperti: sapa('Hello Nyayu') , sapa('Apa Kabar') dan sebagainya.

Apabila pengguna tidak memasukkan argumen, maka fungsi tersebut tidak dapat dijalankan alias akan muncul pesan kesalahan. Pertanyaannya adalah apakah program bisa mengingat sebuah argumen yang telah dimasukkan? sehingga fungsi tersebut dapat dipanggil tanpa argumen. Disini peran closure, Python menyediakan cara untuk mengingat nilai dimasukkan pada fungsi dengan cara seperti yang telah kita gunakan pada contoh di bagian inner function: menyimpan referensi fungsi ke dalam sebuah variabel.

def sapa(pesan):
    def tampilkan():
        print(pesan)

    tampilkan()

sapanyayu = sapa('Halo Nyayu')
sapanyayu()

Pada contoh di atas, fungsi sapa() dengan argumen “Hello Nyayu” disimpan ke dalam variabel bernama sapanyayu sehingga sapanyayu menjadi fungsi. Pada saat pemanggilan sapanyayu(), pesan “Hello Nyayu” masih tetap eksis meskipun fungsi sapa() telah selesai dijalankan. Padahal pada fungsi biasa, seharusnya argumen pada fungsi akan terhapus begitu eksekusi terhadap fungsi selesai. Metode penyimpanan argumen ("Hello Nyayu") terhadap suatu variabel inilah yang disebut dengan closure. Pada contoh fungsi di atas, bagian yang menunjukkan sebagai fungsi closure adalah sebagai berikut:

sapanyayu = sapa('Halo Nyayu')
sapanyayu()

Dengan demikian, jelas bahwa closure adalah sebuah fungsi yang dapat disimpan dalam variabel dan biasa dimanfaatkan untuk mewakili (enclose) sebuah proses pada blok tertentu. Variabel yang menyimpan closure memiliki sifat seperti fungsi yang disimpannya. Dengan menggunakan closure, fungsi dapat mengembalikan objek (atau fungsi lain) dan mengingat lingkungan tempat fungsi tersebut diinisiasi. Nilai yang ada pada outer scope masih diingat meskipun fungsi tersebut telah dihapus. Closure memiliki peran yang sangat penting dalam decorator. Closure digunakan untuk menyimpan nilai fungsi yang telah di dekorasi.

Membuat Decorator Sederhana pada Fungsi Tanpa Parameter

Decorator adalah fungsi yang memodifikasi fungsi lain. Oleh karena itu, decorator merupakan fungsi yang menerima fungsi lain sebagai argumennya (first-class object). Dengan menggunakan decorator, proses mendekorasi fungsi menjadi lebih ringkas dan relatif simpel. Untuk membuat decorator, fungsi untuk decorator tersebut harus terlebih dahulu didefinisikan. Pada contoh kali ini, decorator akan digunakan untuk sebuah fungsi sederhana tanpa parameter.

def inidecorator(fungsinama):
    def halo():
        print('Halo')
        fungsinama()
        print('Apa kabar?')

    return halo

Pada contoh di atas, inidecorator() adalah fungsi yang akan dibuat sebagai decorator. Fungsi di atas akan menerima argumen yang juga merupakan fungsi dengan input berupa “nama”. Fungsi yang diterima kemudian akan dimodifikasi dengan menambahkan kata “Halo” dan “Apa kabar?”. Decorator tersebut akan digunakan untuk mendekorasi fungsi sederhana yang tanpa parameter seperti berikut ini.

def person():
    print('Nyayu')

Fungsi person() akan didekorasi menggunakan fungsi decorator. Fungsi yang telah didekorasi disimpan ke dalam variabel yang diberi nama person seperti berikut:

person = decorator(person)

Nama tersebut tidak harus sama dengan nama fungsi yang di dekorasi, kita dapat memberikan nama apapun sesuai yang diinginkan. Ketika fungsi person() dipanggil, fungsi ini akan dikembalikan menjadi fungsi yang telah didekorasi. Artinya, di dalam fungsi person() sekarang tidak hanya berisi kata “Nyayu” melainkan sebagai berikut:

person()

# Halo
# Nyayu
# Apa kabar?

Sintaks Umum untuk Decorator pada Python

Python memiliki cara yang lebih sederhana untuk mendekorasi fungsi yaitu dengan menggunakan simbol @ diikuti dengan nama fungsi decorator tersebut. Fungsi ini ditempatkan di atas fungsi yang akan didekorasi. Sehingga hasilnya akan menjadi seperti berikut ini:

@inidecorator
def person():
    print('Nyayu')

person()

Fungsi di atas sama dengan fungsi sebelumnya yaitu:

def person():
    print('Nyayu')

person = decorator(person)
person()

Membuat Decorator untuk Fungsi dengan Argumen

Pada contoh sebelumnya, decorator yang dibuat sangat sederhana dan hanya berlaku untuk mendekorasi fungsi yang tidak memiliki parameter. Apabila decorator tersebut dipakai untuk fungsi yang memiliki argumen seperti berikut:

def person2(nama):
    print(nama)

maka akan menghasilkan pesan error karena inner function (fungsi halo()) pada fungsi decorator tidak menerima argumen. Akan tetapi pada saat fungsi dipanggil, argumen ‘Nyayu’ diberikan kepada inner function.

@inidecorator
def person2(nama):
    print(nama)

person('Nyayu')

Supaya decorator @inidecorator dapat mendekorasi fungsi dengan argumen, perlu ditambahkan argumen *args dan **kwargs di dalam fungsi halo() pada decorator.

def inidecorator(fungsinama):
    def halo(*args, **kwargs):
        print('Halo')
        fungsinama(*args,**kwargs)
        print('Apa kabar?')

    return halo

Penambahan argumen *args dan **kwargs menyebabkan fungsi halo() bisa menerima sejumlah argumen. Dengan demikian, @inidecorator bisa mendekorasi fungsi person2() yang menerima input berupa argumen nama.

Penulis: Nyayu Fitria Romadhona Editor : AT, EM

comments powered by Disqus