Semua decorator yang kita tulis sejauh ini menyimpan fungsi asli di closure lalu mengikat nama fungsi ke pembungkus. Efek sampingnya baru terasa ketika program tumbuh: laporan galat menampilkan nama yang salah, docstring menghilang, dan alat bantu dokumentasi menolak mengenali fungsi kita. Chapter ini membuktikan masalah tersebut dengan data nyata, lalu memperbaikinya dengan satu baris dari module functools.
Metadata yang Hilang #
Setiap fungsi Python membawa metadata: __name__ menyimpan nama fungsi, __doc__ menyimpan docstring, dan masih ada beberapa lainnya. Decorator yang menuliskan return pembungkus menimpa metadata fungsi asli dengan metadata milik pembungkus. Mari kita ukur kerusakannya sebelum dan sesudah pembungkusan:
def catat(func):
def pembungkus(*args, **kwargs):
print("mencatat pemanggilan...")
return func(*args, **kwargs)
return pembungkus
def hitung_diskon(harga, persen):
"""Menghitung harga akhir setelah diskon."""
return harga - harga * persen / 100
print("sebelum dibungkus:")
print("nama :", hitung_diskon.__name__)
print("dok :", hitung_diskon.__doc__)
hitung_diskon = catat(hitung_diskon)
print("sesudah dibungkus:")
print("nama :", hitung_diskon.__name__)
print("dok :", hitung_diskon.__doc__)Output yang Dihasilkan:
sebelum dibungkus:
nama : hitung_diskon
dok : Menghitung harga akhir setelah diskon.
sesudah dibungkus:
nama : pembungkus
dok : NoneFungsi masih bekerja dengan benar, tetapi identitasnya berubah: __name__ bernilai "pembungkus" dan __doc__ bernilai None. Pada program kecil, perubahan ini terasa sepele. Pada program besar, __name__ yang salah muncul di laporan log, penelusuran galat (traceback), dan pesan help().
Memulihkan Metadata dengan functools.wraps #
Module functools (standar sejak Python 3.2) menyediakan decorator khusus untuk masalah ini: @functools.wraps(func) ditempel pada pembungkus agar metadata fungsi asli disalin ke pembungkus:
import functools
import inspect
def catat(func):
@functools.wraps(func)
def pembungkus(*args, **kwargs):
print("mencatat pemanggilan...")
return func(*args, **kwargs)
return pembungkus
@catat
def hitung_diskon(harga, persen):
"""Menghitung harga akhir setelah diskon."""
return harga - harga * persen / 100
print("nama :", hitung_diskon.__name__)
print("dok :", hitung_diskon.__doc__)
print("tanda tangan:", inspect.signature(hitung_diskon))
print("hasil:", hitung_diskon(150000, 10))Output yang Dihasilkan:
nama : hitung_diskon
dok : Menghitung harga akhir setelah diskon.
tanda tangan: (harga, persen)
mencatat pemanggilan...
hasil: 135000.0Mari kita bedah letak perbaikannya:
- Baris 1 (
import functools): mengimpor module standar; tidak perlu instalasi apa pun. - Baris 5 (
@functools.wraps(func)): ditempatkan tepat di atasdef pembungkus. Decorator ini menyalin__name__,__doc__,__module__,__qualname__, dan atribut lain darifunckepembungkus, serta menambahkan__wrapped__yang menunjuk balik ke fungsi asli. - Baris 18 (
print("tanda tangan:", inspect.signature(hitung_diskon))): fungsiinspect.signature()mengikuti atribut__wrapped__sehingga tanda tangan asli(harga, persen)bisa ditampilkan — bukan(*args, **kwargs)milik pembungkus.
Kenapa Ini Penting #
Metadata bukan hiasan; tiga kelompok pengguna bergantung padanya:
- Introspeksi dan dokumentasi: fungsi bawaan
help(), moduleinspect, dan generator dokumentasi seperti Sphinx membaca__doc__dan__name__untuk menjelaskan fungsi kita. - Debugging: ketika galat terjadi di dalam fungsi terdekorasi, nama yang muncul pada traceback dan laporan log sebaiknya nama fungsi asli, bukan
pembungkusyang muncul di mana-mana. - Alat otomatis: beberapa library serialisasi dan framework mencari fungsi berdasarkan
__qualname__dan__module__; metadata yang tertimpa membuat fungsi tidak ditemukan.
Dengan ini, rangkaian decorator kita sudah produktif dan ramah terhadap alat bantu. Paruh kedua section berpindah topik: dari membungkus fungsi menjadi memproduksi nilai secara lazy lewat yield dan generator functions.