Memodifikasi Fungsi dengan Python Decorator (Bagian 2)

Photo by virginia lackinger on Unsplash

Artikel ini adalah lanjutan dari bagian pertama yang sudah di-post sebelumnya. Kita akan melanjutkan apa yang sudah dibahas dan masuk ke bagian dimana Python Decorator menjadi sedikit lebih kompleks. Pada artikel sebelumnya ada satu pertanyaan mengenai apakah Decorator dapat menerima argumen? Kita akan mempelajarinya pada artikel ini.

Mendekorasi Method

Pada artikel sebelumnya kita sudah mempelajari cara mendekorasi fungsi, tapi apakah kita bisa mendekorasi sebuah metode dari class. Pada dasarnya metode dari sebuah class adalah fungsi, perbedaannya fungsi ini dibawa menjadi properti sebuah object atau instance dari class tersebut. Mari kita berkenalan terlebih dahulu dengan built-in decorators yang umum digunakan.

Beberapa decorator itu adalah ‘@staticmethod’, ‘@classmethod’, dan ‘@property’.

Pada kode diatas, kita mendefinisikan sebuah class bernama Vector. Class ini merepresentasikan sebuah konsep vektor dua dimensi, yang memiliki komponen x dan y. Perlu diperhatikan jika kita menggunakan __x dan __y, tidak x dan y seperti biasanya. Tujuannya untuk membedakan manakah yang dapat diakses secara bebas, dan mana yang diperuntukan sebagai “private variable”. Meskipun konsep private variable yang umum digunakan di Java, tidak sepenuhnya sama pada Python.

Class Vector memiliki beberapa method. Pertama, kita mendefinisikan sebuah dunder agar bentuk string dari instance class ini lebih mudah dipahami. Kemudian ada tiga method yang didekorasi dengan decorator “@property”, yaitu x, y, dan distance. Decorator property akan mengubah method ini menjadi sebuah variable. Sehingga yang awalnya untuk mengakses variable x, kita harus menulis x(), sekarang kita hanya harus menulis x saja; sama seperti cara mengakses variable pada umumnya.

Selanjutnya adalah setter, decorator ini kita gunakan jikaingin men-set atau mengubah sebuah instance variable. Contohnya pada line 49 dan 50, kita mengubah variable x dan y dari unit_vector, dari yang awalnya <1,1> menjadi <3,3>.

Decorator classmethod biasanya digunakan jika kita ingin membuat sebuah instance yang khusus. Pada method ini, argumen yang digunakan adalah cls yang merujuk pada constructor kelas itu sendiri (__init__) untuk menginisialisasi instance, berbeda dengan self yang lebih sering digunakan. Sehingga sebenarnya Vector(1, 1) dan cls(1, 1) membuat instance yang sama, namun digunakan tempat yang berbeda.

Terakhir mengenai decorator staticmethod. Pada line 53, kita memanggil sebuah method langsung dari class Vector. Tapi bagaimana bisa? Bukannya kita hanya bisa memanggil method dari instance kelas tersebut, bukan dari class-nya langsung? Decorator @staticmethod yang membuat kita bisa melakukan ini. Sekarang coba kalau kita hilangkan line 36, apa yang akan terjadi?

Dimana error ini terjadi dan apa yang menyebabkannya? Error terjadi pada line 55, karena saat kita memanggil method dari sebuah instance, definisinya berubah menjadi calculate_distance(self, tup1, tup2). Sedangkan kita menulisnya hanya calculate_distance(tup1, tup2), maka terjadilah error yang mengatakan jika calculate_distance hanya menerima 2 argumen, tapi 3 yang diberikan. Sehingga agar bisa digunakan dari class maupun instance-nya kita harus menggunakan decorator staticmethod.

Nested Decorator

Sebelumnya kita hanya menggunakan single decorator, namun sebenarnya kita bisa menambahkan berapapun decorator (tentunya akan sangat aneh jika terlalu banyak menggunakan decorator pada satu fungsi). Sedikit flashback ke materi pada artikel sebelumnya.

Kita sudah berkenalan dengan decorator do_ten_times dan timer pada artikel bagian 1, yang satu mengulang operasi fungsi sebanyak 10 kali dan yang kedua menghitung waktu yang dibutuhkan untuk menjalankan sebuah fungsi. Sekarang kita coba untuk mengkombinasikan keduanya. Pada line 35 dan 36, decorator do_ten_times berada di atas timer sehingga bentuk ini sebenarnya adalah syntactic sugar dari

Hasilnya adalah

Decorator timer menghitung setiap kali fungsi berjalan. Bagaimana jika decorator timer berada di atas do_ten_times, apakah hasilnya akan berbeda?

Kali ini fungsi dijalankan sebanyak 10 kali, kemudian dihitung waktu yang dibutuhkan.

Decorator dengan Argument

Sekarang saatnya menjawab pertanyaan yang sudah kita tanyakan pada artikel sebelumnya. Mari kita ubah decorator do_ten_times menjadi repeats yang menjalankan fungsi sebanyak yang kita inginkan.

Pada kode diatas kita membuat decorator bernama repeats dengan keyword argument num_reps yang menentukan berapa kali fungsi akan dijalankan. Kemudian kita lihat jika fungsi say_hi telah di-dekorasi, sehingga jika dijalankan maka akan dilakukan print command sebanyak 5 kali (default argument).

Kita bedah isi dari decorator ini. Pertama kita mendefinisikan fungsi repeats yang menerima keyword argument num_reps, di bagian inilah kita mendefinisikan fungsi dengan argumen yang bisa diberikan pada decorator nantinya. Kemudian terdapat inner function bernama decorator, tugasnya sama seperti yang sudah kita pelajari sebelumnya, mendekorasi sebuah fungsi; perhatikan jika fungsi decorator menerima argumen func, sama seperti decorator tanpa argumen. Bagian paling dalam dari layer ini adalah wrapper dimana fungsi yang akan didekorasi dijalankan. Maka perbedaan dari decorator biasa dan decorator dengan argumen adalah kita menambahkan satu layer tambahan pada decorator dengan argumen, sedangkan sisanya tetap sama.

Stateful Decorators

Selain mendekorasi sebuah fungsi, decorator juga dapat menyimpan variable (state). Kadang hal ini berguna jika kita ingin mengetahui state dari program yang kita tulis, contohnya, berapa kali fungsi tertentu sudah dijalankan pada keseluruhan kode yang kita tulis.

Contoh dari https://realpython.com/primer-on-python-decorators/#fancy-decorators

Decorator count_calls berguna untuk menghitung berapa kali fungsi say_hi dijalankan, yang dalam contoh diatas adalah 2 kali. Perhatikan jika pada decorator count_calls, fungsi wrapper mempunyai internal state variable num_calls. Di line 10, kita menginisialisasi num_calls yang dimiliki oleh fungsi wrapper dan pada line 7, setiap fungsi wrapper dijalankan maka num_calls akan bertambah 1. Perlu diingat jika fungsi say_hi yang sudah didekorasi akan mereferensikan wrapper jika kita tidak menggunakan functools wraps.

Class sebagai Decorator

Kita akan melakukan hal yang berbeda, sebelumnya kita menggunakan fungsi untuk membuat decorator, sekarang kita akan menggunakan class untuk membuat decorator. Ayo kita ubah decorator count_calls menjadi sebuah class.

Function as decorator num_calls sekarang berubah menjadi class as decorator CountCalls. Mengapa kita bisa mengubah class menjadi sebuah decorator? Pada line 28, kita menggunakan CountCalls seperti decorator biasa (tanpa syntactic sugar) saat kita menulis CountCalls(say_ho) hasilnya akan menjadi instance dari class CountCalls. Sehingga saat kita menggunakan say_ho(), kita akan memanggil dunder __call__. Perlu diperhatikan juga kita menggunakan functools.update_wrapper, tidak functools.wraps, namun kegunaannya tetap sama.

Class dengan methods sebagai decorator

Sekarang kita kumpulkan beberapa decorator ke dalam sebuah class.

Di atas kita mendefinisikan sebuah class bernama MyClass yang memiliki dua method yang akan kita gunakan sebagai decorator. Penggunaan kedua method ini tidak begitu berbeda dengan yang kita lakukan sebelumnya, bedanya dalam pendefinisiannya kita harus menambahkan argumen self (perhatikan line 9 dan 24) dan pada pendekorasiannya kita tambahkan instance dari class tersebut (perhatikan pada line 39 dan 43).

Pengaplikasian decorator (Bagian 2)

Membuat singletons

Singleton adalah sebuah class yang hanya mempunyai satu instance. Mari kita definisikan decorator yang dapat mendekorasi sebuah class agar hanya dapat membuat satu instance saja.

Perhatikan line 6 dan 7, disinilah bagian yang membuat decorator singleton spesial. Saat pertama kali membuat instance dari class Single, wrapper.instance adalah None, sehingga statement not wrapper.instance sama dengan True. Maka line 7 dijalankan dan di line selanjutnya instance class tersebut di-return. Jika kita membuat instance selanjutnya, maka line 7 tidak dijalankan (if statement bernilai False), dan mengeluarkan instance sebelumnya yang disimpan di wrapper.instance. Akhirnya saat kita cek apakah instance one dan two sama, maka nilai yang dikeluarkan adalah True. Anda bisa mencoba apa yang terjadi jika anda menghilangkan line 14.

Caching

Caching dapat membantu kita mengurangi jumlah operasi yang berulang dengan tidak melakukan operasi yang sama berulang-ulang kali, contohnya saat menggunakan recursive. Sekarang mari kita buat sebuah fungsi recursive sederhana untuk mendapatkan barisan fibonacci.

Diatas, kita menggunakan decorator yang pernah kita tulis sebelumnya untuk menghitung berapa kali fungsi fibonacci dijalankan. Untuk nilai fibonacci(19), jumlah operasi yang terjadi adalah 13529. Kita bisa mengurangi jumlah operasi ini dengan memanfaatkan decorator.

Penggunaan decorator cache menggunakan sifat yang sudah kita praktikan sebelumnya, yaitu menyimpan state dari suatu fungsi. Pada line 19, kita membuat cache_key yang akan menjadi signature dari pemanggilan suatu fungsi. Di line berikutnya kita pasangkan key-value pair dari cache_key dengan hasil dari pemanggilan func. Sehingga saat fungsi ini berjalan dengan argumen yang telah dijalankan sebelumnya, maka dia hanya tinggal melihat ke dictionary cache dan mendapatkan value-nya. Namun jika operasi ini belum pernah dijalankan, maka akan dibuat cache_key untuk menjadi signature operasi ini dan menyimpan nilainya pada dictionary cache.

Kesimpulan

Kita sudah mempelajari seluk beluk dari decorator, baik dari fungsi sebagai decorator, class sebagai decorator, method sebagai decorator, dll. Yang perlu diingat dan dipahami adalah pada dasarnya decorator itu adalah higher-order function yang menerima fungsi dan mengeluarkan fungsi. Dengan dasar pemahaman ini mungkin kita akan lebih mudah untuk berkreasi dengan decorator dan menyelesaikan problem yang spesifik dengan efisien dan mudah.

Terima kasih telah membaca.

Sumber:

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

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store