Decorator @ukur_waktu pada chapter sebelumnya bekerja untuk semua fungsi, tetapi perilakunya seragam — pengaturannya terkunci di dalam kode. Bagaimana jika kita ingin decorator yang bisa diatur pemakainya: ulang pemanggilan sebanyak n kali, atau tampilkan log dengan prefix "INFO" atau "DEBUG"? Untuk itu, decorator perlu menerima argument sendiri, dan strukturnya bertambah satu lapisan closure.
Tiga Lapis Closure #
Decorator biasa punya dua lapis: badan decorator yang menerima func, dan badan pembungkus() yang berjalan pada tiap pemanggilan. Ketika decorator perlu argument, kita membutuhkan tiga lapis:
lapis 1: factory menerima pengaturan -> mengembalikan decorator
lapis 2: decorator menerima func -> mengembalikan pembungkus
lapis 3: pembungkus berjalan di setiap pemanggilanPerbedaan mendasarnya: pada @ukur_waktu, interpreter Python mengirim objek fungsi langsung ke decorator ukur_waktu. Namun pada @ulangi(3), yang dikirim lebih dulu adalah angka 3. Ekspresi ulangi(3) berjalan seketika sebagai panggilan fungsi biasa, dan hasilnya — si decorator sejati — barulah menerima fungsi yang didekorasi.
Decorator repeat(n) #
Kita terjemahkan struktur tiga lapis tersebut menjadi decorator @ulangi(n):
def ulangi(n):
def dekorator(func):
def pembungkus(*args, **kwargs):
for _ in range(n):
hasil = func(*args, **kwargs)
return hasil
return pembungkus
return dekorator
@ulangi(3)
def kirim_pesan(nama):
print(f"pesan terkirim ke {nama}")
return "semua terkirim"
print(kirim_pesan("Budi"))Output yang Dihasilkan:
pesan terkirim ke Budi
pesan terkirim ke Budi
pesan terkirim ke Budi
semua terkirimMari kita bedah ketiga lapisnya:
- Baris 1 (
def ulangi(n):): lapis factory. Saat interpreter membaca@ulangi(3), pemanggilanulangi(3)berjalan seketika dan mengembalikandekoratordengan nilain = 3tersimpan di closure. - Baris 2 (
def dekorator(func):): lapis decorator. Objek fungsikirim_pesandikirim ke lapis ini, sama seperti decorator biasa. - Baris 3-7 (
def pembungkus(*args, **kwargs):): lapis pembungkus. Loopfor _ in range(n)memanggil fungsi asli sebanyak tiga kali; nilai kembalian pemanggilan terakhir diteruskan ke pemanggil. - Baris 15 (
print(kirim_pesan("Budi"))): satu pemanggilan dari luar memicu tiga pemanggilan fungsi asli di dalam.
Prefix Logger #
Contoh kedua memanfaatkan argument berupa string. Decorator @log_dengan_prefix(prefix) mencatat setiap pemanggilan dengan label yang bisa diatur:
def log_dengan_prefix(prefix):
def dekorator(func):
def pembungkus(*args, **kwargs):
print(f"[{prefix}] memanggil {func.__name__}{args}")
hasil = func(*args, **kwargs)
print(f"[{prefix}] {func.__name__} selesai")
return hasil
return pembungkus
return dekorator
@log_dengan_prefix("INFO")
def tambah_pesanan(a, b):
return a + b
@log_dengan_prefix("DEBUG")
def cek_stok(nama_barang):
return f"stok {nama_barang}: 12"
print(tambah_pesanan(3, 4))
print(cek_stok("kopi"))Output yang Dihasilkan:
[INFO] memanggil tambah_pesanan(3, 4)
[INFO] tambah_pesanan selesai
7
[DEBUG] memanggil cek_stok('kopi',)
[DEBUG] cek_stok selesai
stok kopi: 12Dua fungsi memakai decorator yang sama dengan pengaturan berbeda: tambah_pesanan() dicatat dengan prefix INFO, sedangkan cek_stok() dengan prefix DEBUG. Masing-masing prefix tersimpan terpisah di closure masing-masing pembungkus.
Perhatikan detail pada keluaran ('kopi',): {args} mencetak argument positional sebagai tuple, dan tuple berisi satu elemen selalu ditampilkan dengan koma di belakang. Koma tersebut bagian dari notasi tuple, bukan bagian dari data.
Bayangan Keterbacaan #
Struktur tiga lapis memang kuat, tetapi setiap lapis menambah beban membaca:
| Lapis | Menerima | Dijalankan saat | Nama di contoh |
|---|---|---|---|
| 1 | pengaturan (n, prefix) | @ulangi(3) dieksekusi | ulangi, log_dengan_prefix |
| 2 | objek fungsi | setelah lapis 1 selesai | dekorator |
| 3 | argument pemanggilan | setiap kali fungsi dipanggil | pembungkus |
Ada satu masalah halus yang belum kita tangani: semua contoh di chapter ini mengganti nama fungsi dengan pembungkus, sehingga __name__ dan docstring fungsi asli menghilang. Chapter functools.wraps menunjukkan cara memulihkannya.