Advertisement
  1. Code
  2. Coding Fundamentals
  3. Functional Programming

Menerapkan Konsep DRY pada Kode Python-mu dengan Decorator

Scroll to top
Read Time: 8 min

() translation by (you can also view the original English article)

Decorator adalah salah satu fitur terbaik dari Python, namun untuk programmer Python pemula, decorator itu seperti sihir. Tujuan dari artikel ini adalah untuk memahami dengan mendalam tentang mekanisme di balik Python decorator.

Berikut adalah yang akan kamu pelajari:

  • apakah Python decorator dan kegunaannya
  • bagaimana cara mendefinisikan decorator kita sendiri
  • contoh penggunaan decorator di dunia nyata dan bagaimana mereka bekerja
  • bagaimana menulis kode yang lebih baik menggunakan decorator

Pengenalan

Jika kamu belum pernah melihat decorator (atau mungkin tidak tahu kamu berurusan dengan decorator), mereka terlihat seperti ini:

1
@decorator
2
def function_to_decorate():
3
    pass

Kamu biasanya menemukan decorator di atas definisi sebuah fungsi dan dimulai dengan @. Decorator sangat baik untuk membuat kodemu tidak banyak diulang, alias DRY (Don't Repeat Yourself), dan decorator juga membuat kode kamu lebih mudah dibaca.

Masih bingung? Jangan, karena decorator hanyalah fungsi Python. Benar! Kamu sudah tahu bagaimana membuatnya. Justru dasar di balik decorator adalah pembentukan fungsi. Berikut adalah contohnya:

1
def x_plus_2(x):
2
    return x + 2
3
4
print(x_plus_2(2))                      # 2 + 2 == 4

5
6
7
def x_squared(x):
8
    return x * x
9
10
print(x_squared(3))                     # 3 ^ 2 == 9

11
12
13
# Let's compose the two functions for x=2

14
print(x_squared(x_plus_2(2)))           # (2 + 2) ^ 2 == 16

15
print(x_squared(x_plus_2(3)))           # (3 + 2) ^ 2 == 25

16
print(x_squared(x_plus_2(4)))           # (4 + 2) ^ 2 == 36

Bagaimana jika kita ingin membuat fungsi lain, x_plus_2_squared? Mencoba membuat fungsi tersebut akan percuma:

1
x_squared(x_plus_2)  # TypeError: unsupported operand type(s) for *: 'function' and 'function'

Kamu tidak bisa membuat fungsi dengan cara seperti ini karena kedua fungsi perlu menerima angka sebagai argumen. Tapi, kode ini akan bekerja:

1
# Let's now create a proper function composition without actually applying the function

2
x_plus_2_squared = lambda x: x_squared(x_plus_2(x))
3
4
print(x_plus_2_squared(2)) # (2 + 2) ^ 2 == 16

5
print(x_plus_2_squared(3)) # (3 + 2) ^ 2 == 25

6
print(x_plus_2_squared(4)) # (4 + 2) ^ 2 == 36

Kita definisikan bagaimana x_squared bekerja. Jika kita ingin x_squared bisa dibentuk, fungsi tersebut harus:

  1. Menerima fungsi sebagai argumen
  2. Mengembalikan fungsi lain

Kita beri nama versi yang bisa disusun dari x_squared dengan nama squared.

1
def squared(func):
2
    return lambda x: func(x) * func(x)
3
4
print(squared(x_plus_2)(2)) # (2 + 2) ^ 2 == 16

5
print(squared(x_plus_2)(3)) # (3 + 2) ^ 2 == 25

6
print(squared(x_plus_2)(4)) # (4 + 2) ^ 2 == 36

Sekarang setelah kita mendefinisikan fungsi squared sedemikian rupa agar bisa disusun, kita bisa menggunakannya dengan fungsi apa saja. Berikut adalah beberapa contoh:

1
def x_plus_3(x):
2
    return x + 3
3
4
def x_times_2(x):
5
    return x * 2
6
7
print(squared(x_plus_3)(2))  # (2 + 3) ^ 2 == 25

8
print(squared(x_times_2)(2)) # (2 * 2) ^ 2 == 16

Kita bisa bilang bahwa squared mendekorasi fungsi x_plus_2, x_plus_3, dan x_times_2. Kita sudah mendekati notasi standar decorator. Lihatlah kode berikut:

1
x_plus_2 = squared(x_plus_2)  # We decorated x_plus_2 with squared

2
print(x_plus_2(2))            # x_plus_2 now returns the decorated squared result: (2 + 2) ^ 2 

Sekian! x_plus_2 sekarang fungsi Python yang sudah didekorasi. Di sinilah notasi @ dimanfaatkan:

1
def x_plus_2(x):
2
    return x + 2
3
4
x_plus_2 = squared(x_plus_2)
5
6
# ^ This is completely equivalent with: 

7
8
@squared
9
def x_plus_2(x):
10
     return x + 2

Sebenarnya, notasi @ adalah format gula buatan. Kita coba sekarang:

1
@squared
2
def x_times_3(x):
3
    return 3 * x
4
5
print(x_times_3(2)) # (3 * 2) ^ 2 = 36.

6
# It might be a bit confusing, but by decorating it with squared, x_times_3 became in fact (3 * x) * (3 * x)

7
8
@squared
9
def x_minus_1(x):
10
    return x - 1
11
12
print(x_minus_1(3)) # (3 - 1) ^ 2 = 4

Jika squared adalah decorator pertama yang kamu tulis, beri selamat dirimu sendiri. Kamu menangkah satu dari konsep paling rumit dari Python. Sepanjang jalan, kamu mempelajari satu fitur dasar dari bahasa pemrograman fungsional: penyusunan fungsi atau function composition.

Buat Decorator-mu Sendiri

Decorator adalah fungsi yang menerima argumen berupa fungsi dan mengembalikan fungsi lain. Walaupun begitu, kerangka umum untuk mendefinisikan decorator adalah sebagai berikut:

1
def decorator(function_to_decorate):
2
    # ...

3
    return decorated_function

Jika kamu tidak tahu, kamu bisa mendefinisikan fungsi di dalam fungsi. Umumnya, decorated_function akan didefinisikan di dalam decorator.

1
def decorator(function_to_decorate):
2
    def decorated_function(*args, **kwargs):
3
        # ... Since we decorate `function_to_decorate`, we should use it somewhere inside here

4
    return decorated_function

Mari kita lihat contoh lain yang lebih praktis:

1
import pytz
2
from datetime import datetime
3
4
def to_utc(function_to_decorate):
5
    def decorated_function():
6
        # Get the result of function_to_decorate and transform the result to UTC

7
        return function_to_decorate().astimezone(pytz.utc)
8
    return decorated_function
9
10
@to_utc
11
def package_pickup_time():
12
    """ This can come from a database or from an API """
13
    tz = pytz.timezone('US/Pacific')
14
    return tz.localize(datetime(2017, 8, 2, 12, 30, 0, 0))
15
16
@to_utc
17
def package_delivery_time():
18
    """ This can come from a database or from an API """
19
    tz = pytz.timezone('US/Eastern')
20
    return tz.localize(datetime(2017, 8, 2, 12, 30, 0, 0)) # What a coincidence, same time different timezone!

21
22
print("PICKUP: ", package_pickup_time())      # '2017-08-02 19:30:00+00:00'

23
print("DELIVERY: ", package_delivery_time())  # '2017-08-02 16:30:00+00:00'

Bagus! Sekarang kamu bisa memastikan semua di dalam aplikasimu sudah menggunakan standar timezone UTC.

Contoh Praktis

Penggunaan lain yang sangat populer dan klasik untuk decorator adalah menyimpan hasil dari fungsi:

1
import time
2
3
def cached(function_to_decorate):
4
    _cache = {} # Where we keep the results

5
    def decorated_function(*args):
6
        start_time = time.time()
7
        print('_cache:', _cache)
8
        if args not in _cache:
9
            _cache[args] = function_to_decorate(*args) # Perform the computation and store it in cache

10
        print('Compute time: %ss' % round(time.time() - start_time, 2))
11
        return _cache[args]
12
    return decorated_function
13
14
@cached
15
def complex_computation(x, y):
16
    print('Processing ...')
17
    time.sleep(2)
18
    return x + y
19
20
print(complex_computation(1, 2)) # 3, Performing the expensive operation

21
print(complex_computation(1, 2)) # 3, SKIP performing the expensive operation

22
print(complex_computation(4, 5)) # 9, Performing the expensive operation

23
print(complex_computation(4, 5)) # 9, SKIP performing the expensive operation

24
print(complex_computation(1, 2)) # 3, SKIP performing the expensive operation

Jika kamu melihat kode sekilas, kamu mungkin protes. Decorator tidak bisa digunakan ulang! Jika kita mendekorasi fungsi lain (misalnya another_complex_computation) dan memanggilnya dengan parameter yang sama, maka kita akan mendapat hasil yang disimpan dari fungsi complex_computation. Hal ini tidak akan terjadi. Decorator itu bisa digunakan kembali, berikut alasannya:

1
@cached
2
def another_complex_computation(x, y):
3
    print('Processing ...')
4
    time.sleep(2)
5
    return x * y
6
    
7
print(another_complex_computation(1, 2)) # 2, Performing the expensive operation

8
print(another_complex_computation(1, 2)) # 2, SKIP performing the expensive operation

9
print(another_complex_computation(1, 2)) # 2, SKIP performing the expensive operation

Fungsi cached dipanggil sekali untuk setiap fungsi yang didekorasi, jadi variabel _cache dibuat setiap kali dan hidup dalam konteks tersebut. Mari kita coba:

1
print(complex_computation(10, 20))           # -> 30

2
print(another_complex_computation(10, 20))   # -> 200

Decorator di Alam Liar

Decorator yang kita buat, seperti yang kamu perhatikan, sangat berguna. Decorator sangat berguna bahkan versi yang lebih rumit dan kuat sudah ada di modul standar functools. Decorator tesebut bernama lru_cache. LRU adalah singkatan dari Least Recently User, sebuah strategi caching.

1
from functools import lru_cache
2
3
@lru_cache()
4
def complex_computation(x, y):
5
    print('Processing ...')
6
    time.sleep(2)
7
    return x + y
8
9
print(complex_computation(1, 2)) # Processing ... 3

10
print(complex_computation(1, 2)) # 3

11
print(complex_computation(2, 3)) # Processing ... 5

12
print(complex_computation(1, 2)) # 3

13
print(complex_computation(2, 3)) # 5

Salah satu penggunaan favorit saya adalah pada framework web Flask. Decoratornya sangat rapi sehingga potongan kode ini adalah hal pertama yang kamu lihat di situs Flask. Berikut adalah potongan kodenya:

1
from flask import Flask
2
3
app = Flask(__name__)
4
5
@app.route("/")
6
def hello():
7
    return "Hello World!"
8
9
if __name__ == "__main__":
10
    app.run()

Decorator app.route mengirimkan fungsi hello sebagai request handler untuk rute "/". Kesederhanaannya mengagumkan.

Penggunaan rapi lainnya adalah di dalam Django. Biasanya aplikasi web memiliki dua tipe halaman:

  1. halaman yang bisa kamu lihat tanpa diautentifikasi (halaman depan, halaman awal, blog post, login, register)
  2. halaman di mana kamu harus terautenfitikasi untuk melihatnya (profile setting, inbox, dashboard)

Jika kamu coba melihat halaman jenis kedua, kamu biasanya diredirect ke halaman login. Berikut cara mengimplementasinya pada Django:

1
from django.http import HttpResponse
2
from django.contrib.auth.decorators import login_required
3
4
# Public Pages

5
6
def home(request):
7
    return HttpResponse("<b>Home</b>")
8
9
def landing(request):
10
    return HttpResponse("<b>Landing</b>")
11
12
# Authenticated Pages

13
14
@login_required(login_url='/login')
15
def dashboard(request):
16
    return HttpResponse("<b>Dashboard</b>")
17
18
@login_required(login_url='/login')
19
def profile_settings(request):
20
    return HttpResponse("<b>Profile Settings</b>")

Lihat bagaimana rapinya halaman privat ditandai dengan login_required. Selama melihat kode, sangat mudah u ntuk pembaca halaman mana yang membutuhkan user untuk login dan halaman mana yang tidak.

Kesimpulan

Saya harap kamu senang belajar tentang decorator karena mereka adalah fitur Python yang sangat berguna. Berikut adalah beberapa hal untuk diingat:

  • Menggunakan decorator dengan benar bisa membuat kode-mu lebih baik, lebih bersih, dan lebih indah.
  • Menggunakan decorator bisa membantu kode kamu mengikuti konsep DRY, memindahkan kode yang sama dari dalam fungsi ke decorator.
  • Semakin banyak kamu gunakan decorator, kamu akan menemui lebih banyak penggunaan decorator yang lebih baik dan lebih rumit.

Ingatlah untuk melihat apa yang dijual dan bisa dipelajari di Envator Market, dan jangan ragu untuk bertanya dan memberi pendapat di komentar di bawah.

Sekian tentang decorator. Selamat mendekorasi!

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.