Memodifikasi Fungsi dengan Python Decorator (Bagian 1)

Photo by corina ardeleanu on Unsplash

Pada artikel kali ini kita akan membahas salah satu “fitur” yang banyak digunakan di bahasa pemrograman Python, yaitu Decorator. Pada aplikasinya decorator memberi kita bentuk yang lebih sederhana saat kita bergelut dengan kode yang ingin di-”dekorasi” oleh sebuah higher-order function. Umumnya kita akan menggunakan decorator jika ada beberapa fungsi yang memiliki tugas berbeda, tetapi terdapat kemiripan. Sehingga kita bisa menempatkan “sifat” kemiripan ini pada satu fungsi dan fungsi inilah yang akan mendekorasi fungsi-fungsi lain. Akhirnya membuat kode kita mengikuti kaidah DRY seperti yang seharusnya.

Secara definisi Decorator adalah sebuah fungsi yang menerima fungsi lain sebagai argumen dan mengeluarkan fungsi tersebut dengan penambahan sifat dan fungsionalitas tertentu. Sudah cukup penjelasan tanpa contoh, sekarang kita masuk ke bagian utama, dimulai dengan fungsi sederhana.

Function

Sebelum memulai penjelasan mengenai higher-order functions atau decorator mari kita mulai dengan penjelasan mengenai fungsi. Function menerima argumen (input) dan mengeluarkan nilai (output). Apapun yang kita tulis di dalamnya akan mengolah input tersebut dan mengeluarkan output berdasarkan input yang diberikan. Selain menerima input dan mengeluarkan ouput, fungsi juga bisa memberikan efek lain (contohnya print statement).

Fungsi diatas akan melakukan print sebanyak num_times sebagai “efek lain” dan mengeluarkan nilai pada line 4. Dengan kata lain fungsi ini mempunyai argumen bernama num_times, mengeluarkan nilai “Hi, It’s nice to see you!”, dan memiliki efek lain print “Hi” sejumlah nilai num_times yang diberikan. Cukup sederhana bukan? Sekarang masuk ke higher-order-functions dan apa perbedaannya dengan fungsi biasanya.

Higher-Order Functions dan First-Class Objects

Definisi Higher-Order Function adalah sebuah fungsi yang melakukan salah satu atau keduanya dari hal berikut ini, yang pertama dia menerima satu atau lebih fungsi sebagai argumen dan yang kedua mengeluarkan sebuah fungsi sebagai hasilnya. Sedangkan First-Class Objects, secara sederhana adalah sebuah object atau entitas yang dapat menjadi argumen fungsi lain ataupun menjadi nilai keluaran, sama seperti object lain (int, string, list, dll…)

Fungsi pertama dan kedua seperti yang sudah kita ketahui, adalah fungsi biasa. Fungsi pertama menerima satu argumen int (meskipun tidak ada pengecekan type) dan mengeluarkan string. Fungsi kedua menerima satu argumen string dan mengeluarkan string, Sedangkan fungsi ketiga menerima fungsi lain dan mengeluarkan nilai yang diberikan pada fungsi lain tersebut.

Inner functions

Kita juga bisa mendefinisikan sebuah fungsi di dalam fungsi lain. Namun sama seperti variable yang hanya ada dalam suatu scope, fungsi yang didefinisikan dalam fungsi lain juga hanya dapat diakses dalam fungsi parent tersebut. Mari kita tuliskan sebuah contoh:

Pada contoh diatas terdapat dua inner function tanpa argumen dan tidak mengeluarkan nilai apapun. Perlu diperhatikan juga di line 8 dan 9 kita memanggil kedua inner function ini, apa yang terjadi jika kita tidak menjalankannya? Tentu saja tidak akan ada print command yang dijalankan.

Mengeluarkan fungsi dari fungsi

Mari membuat fungsi yang berbeda dan kita namakan simple_calculator.

Fungsi diatas relatif lebih panjang dibandingkan fungsi-fungsi yang sudah dituliskan sebelumnya (tentu saja fungsi diatas dapat ditulis lebih singkat menggunakan lambda expression), namun tidak lebih kompleks secara fungsionalitas bukan? Sekarang coba kita perhatikan mulai dari line 17 sampai akhir. Setelah keyword return, kita menulis fungsi yang ingin dikeluarkan, yang tergantung dengan argumen yang diberikan pada parent function, tanpa tanda kurung (misalnya add, bukan add()). Hal ini karena kita ingin mendapatkan reference dari fungsi yang diinginkan, bukan menjalankannya. Apa yang terjadi jika kita menambahkan tanda kurung? Silakan anda coba.

Decorator sederhana

Setelah memahami kapabilitas apa saja yang bisa dicapai dari suatu fungsi, sekarang kita masuk ke main course. Dimulai dari contoh berikut:

Mari kita bedah kode diatas. Dimulai dari higher-order function bernama simple_decorator. Fungsi ini menerima fungsi lain, dan mempunyai inner function bernama wrapper. Di dalam fungsi wrapper, kita melakukan print statementBefore calling the function”, selanjutnya menjalankan fungsi yang dikirim ke higher-order function (func()), dan menjalankan print comman After calling the function”. Kemudian fungsi simple_decorator mengeluarkan fungsi wrapper tersebut. Maka fungsi say_hi pada line 15, akan mereferensikan fungsinya sendiri ataukah wrapper?. Sayangnya sekarang fungsi say_hi mereferensikan fungsi wrapper (dapat anda buktikan dengan menjalankan command say_ho.__name__). Nanti kita akan mempelajari solusi atas masalah ini.

Decorator to the rescue… Kita dapat menulis fungsi diatas dengan python decorator syntactic sugar. Akan seperti apa bentuknya?

Perhatikan pada line 11, kita menuliskan “@simple_decorator” di atas definisi fungsi say_ho. Perubahan yang sederhana, tapi menurut saya sendiri membuatnya lebih elegan. Kita tidak perlu menambahkan statement untuk reassignment dari fungsi awal. Tambahkan saja syntax decorator seperti yang kita lakukan. Hasilnya fungsi say_ho sudah termodifikasi.

Sekarang kita tuliskan contoh kedua. Tugasnya adalah bagaimana cara menuliskan sebuah decorator yang dapat memodifikasi suatu fungsi agar fungsi tersebut dapat dijalankan sepuluh kali. Bagaimana?

Decorator do_ten_times menjalankan fungsi yang didekorasi sebanyak sepuluh kali. Kalau kita mau menjalankannya dua kali saja, haruskah kita membuat decorator yang berbeda? Tentu saja tidak, kita hanya perlu decorator yang menerima argumen. Hal ini akan kita pelajari nanti.

Mendekorasi fungsi dengan argumen

Sebelumnya, kita mendekorasi fungsi yang tidak menerima argumen, bagaimana jika kita ingin mendekorasi fungsi, tapi dia menerima argumen. Kita coba saja decorator do_ten_times melakukan hal ini.

Jika kita menjalankan kode diatas, apa yang akan terjadi? Voilà error-lah yang kita dapat. Tapi tidak masalah, kita akan mengatasi masalah ini.

Kembali ke konsep yang sudah dijelaskan sebelumnya. Sebuah fungsi yang didekorasi saat ini mereferensikan wrapper dari decorator yang kita definisikan. Coba kita menjalankan command introduce_myself.__name__ setelah fungsi ini didekorasi.

Catatan: func.__name__ akan mengeluarkan nilai nama fungsi tersebut.

Dari kode diatas, kita ketahui jika fungsi yang didekorasi sekarang mereferensikan wrapper dari decorator, sedangkan fungsi yang tidak didekorasi tetap menjadi fungsi itu sendiri. Sehingga ada dua problem yang harus diselesaikan, pertama bagaimana cara agar fungsi yang didekorasi dapat menjadi dirinya sendiri dan bagaimana fungsi yang didekorasi dapat mempunyai argumen.

Solusi untuk problem pertama adalah dengan menggunakan module functools.

Perhatikan jika pada line 7, kita menambahkan satu line tambahan menggunakan functools untuk menyelesaikan masalah kita. Fungsi functools.wrap pada dasarnya akan mengubah properti suatu fungsi seperti __name__ atau __doc__ (saat memanggil fungsi help) agar fungsi itu tetap mereferensikan dirinya sendiri, tidak mereferensikan wrapper seperti yang sebelumnya. Jadi masalah pertama selesai, sekarang kita selesaikan masalah kedua, bagaimana agar fungsi yang didekorasi dapat memiliki argumen?

Pada line 8 dan 10, kita menuliskan *args dan **kwargs. Expression ini yang biasa digunakan sebagai positional arguments dan keyword arguments. Agar kita dapat menambahkan berapapun jumlah positional arguments dan apapun keyword arguments.

Fungsi yang terdekorasi dan mengeluarkan nilai

Mari kita ubah sedikit agar fungsi introduce_myself dapat mengeluarkan nilai, tidak melakukan perintah print.

Kode diatas jika dijalankan hanya akan mem-print “Hi, my name is John” sebanyak satu kali pada line 21, karena pada line 10, fungsi introduce_myself berjalan tanpa melakukan print, berbeda dengan contoh yang sebelumnya.

Contoh pengaplikasian decorator

Kita sudah belajar cukup banyak konsep tentang decorator, tapi apakah ada pengaplikasiannya dalam dunia nyata? Tentu ada. Mari kita lihat beberapa contoh diantaranya.

Decorator timer akan memberitahu kita berapa lama suatu fungsi untuk secara penuh dieksekusi. Pada fungsi wrapper, fungsi yang akan didekorasi dijalankan diantara dua time mark dan dengan melakukan pengurangan antara waktu akhir dan awal, maka kita dapat durasi menjalankan fungsi tersebut.

Saat kita membuat suatu aplikasi tentu saja kita akan menggunakan beberapa plugins, agar kita tidak perlu menulis suatu kode dari awal dan tinggal hanya menggunakan apa yang sudah ada (don’t reinvent the wheel). Namun kita juga harus meregister plugins apa saja yang digunakan agar terhindar dari menggunakan plugins yang tidak perlu dan membuang-buang resource. Lalu bagaimana cara menggunakan decorator untuk meregister plugins?

Perhatikan jika fungsi register tidak memiliki fungsi wrapper ataupun inner function karena memang hal ini bukanlah keharusan, karena pada dasarnya decorator adalah higher-order function yang menerima fungsi dan mengeluarkan fungsi. Kemudian pada fungsi say_randomly, fungsi ini akan memilih salah satu dari fungsi yang sudah di-register dan menjalankannya.

Jika anda pernah menggunakan Flask untuk membuat aplikasi web sebelumnya, pasti anda telah sering melihat syntax decorator dengan penggunaannya baik dari yang bawaan Flask itu sendiri maupun buatan pribadi. Berikut ini adalah salah satu peran decorator pada Flask.

Decorator login_required akan memastikan jika hanya orang yang sudah login saja yang dapat mengakses halaman tertentu. Meskipun baiknya anda menggunakan fungsionalitas dari extension Flask-Login saja, karena extension ini dapat mempermudah pekerjaan anda.

Akhirnya kita sampai pada akhir dari artikel kali ini. Sudah cukup banyak penjelasan mengenai konsep fungsi, higher-order function, first-class object, inner function, decorator dan pengaplikasiannya. Sedikit review, apakah decorator itu? decorator adalah higher-order function yang bisa menerima fungsi sebagai argumen dan mengeluarkan fungsi. Jadi jika seseorang bertanya kepada anda apa itu decorator, anda bisa menjawabnya dengan jawaban yang sederhana. Tapi tenang karena kita belum membahas semua hal tentang decorator dan masih ada pertanyaan yang belum dijawab oleh artikel ini, yaitu apakah decorator dapat menerima argumen? Jawabannya bisa, dan kita akan pelajari itu pada bagian kedua. Terima kasih telah membaca.

I am a Software Engineer and Data Science Enthusiast. Love to learn and write. LinkedIn: https://www.linkedin.com/in/agus-richard/

I am a Software Engineer and Data Science Enthusiast. Love to learn and write. LinkedIn: https://www.linkedin.com/in/agus-richard/