Decorator functools.wraps sudah kita pakai saat membangun decorator pada Chapter functools.wraps — tetapi module functools berisi lebih dari sekadar itu. Module ini adalah kotak perkakas untuk mengelola function sebagai objek: menyimpan hasilnya, mengunci sebagian argumennya, dan memilih implementasi berdasarkan tipe data. Pada Chapter ini kita bedah enam alat yang paling sering muncul di kode produksi.
lru_cache: Memoization Satu Baris #
Function rekursif Fibonacci yang naif menghitung ulang nilai yang sama berkali-kali. Decorator @lru_cache menyimpan setiap hasil pemanggilan sehingga perhitungan sebelumnya tinggal dibaca ulang:
from functools import lru_cache
@lru_cache(maxsize=None)
def fib(n):
if n < 2:
return n
return fib(n - 1) + fib(n - 2)
print(fib(35))
print(fib.cache_info())Output yang Dihasilkan:
9227465
CacheInfo(hits=33, misses=36, maxsize=None, currsize=36)Mari kita bedah kode di atas:
- Baris 4 (
@lru_cache(maxsize=None)): membungkusfibdengan cache tanpa batas. Setiap hasil pemanggilan disimpan berdasarkan argumennya. - Baris 11 (
fib(35)): tanpa cache, panggilan ini mengeksekusi puluhan juta pemanggilan rekursif; dengan cache, hanya 36 nilai berbeda yang dihitung sekali. - Baris 12 (
fib.cache_info()): decorator menempelkan method ini ke function yang dibungkusnya. Nilaimisses=36berarti 36 nilai dihitung sungguhan, sedangkanhits=33berarti 33 pemanggilan dijawab langsung dari cache.
Sejak Python 3.9 tersedia alias yang lebih ringkas: @functools.cache setara dengan @lru_cache(maxsize=None). Gunakan lru_cache(maxsize=128) saat data besar dan kita ingin cache membuang entri yang lama tidak dipakai.
partial dan reduce: Membekukan Argumen dan Melipat Data #
Function partial() membuat function baru dengan sebagian argumen yang sudah terisi — teknik yang disebut currying sebagian:
from functools import partial
def diskon(harga, persen):
return harga - (harga * persen / 100)
diskon_member = partial(diskon, persen=20)
print(diskon_member(150000))
print(diskon(150000, persen=20))Output yang Dihasilkan:
120000.0
120000.0Variabel diskon_member adalah function diskon dengan argumen persen yang permanen bernilai 20; kita cukup menyuplai harga saat memanggilnya. Pola ini sangat berguna saat sebuah function butuh banyak argumen tetapi API yang memanggilnya hanya mengizinkan satu argumen — misalnya sorted(key=...) atau handler add_done_callback().
Sementara reduce() melipat sebuah iterable menjadi satu nilai dengan function penggabung, melanjutkan materi pada Chapter map, filter, dan reduce:
from functools import reduce
penjualan = [120, 80, 200, 150]
total = reduce(lambda a, b: a + b, penjualan)
print(total)Output yang Dihasilkan:
550Function lambda dijalankan berantai: ((120 + 80) + 200) + 150 = 550. Untuk kasus penjumlahan, sum() tetap lebih jelas; reduce() unggul saat operasi penggabungannya khusus, dan akan lebih rapi lagi dipasangkan dengan operator.mul seperti pada Chapter Module operator.
cached_property: Property yang Dihitung Sekali #
Decorator @cached_property menyimpan hasil perhitungan property ke dalam instance, sehingga perhitungan mahal hanya terjadi pada akses pertama:
from functools import cached_property
class Katalog:
def __init__(self, daftar_harga):
self._daftar_harga = daftar_harga
@cached_property
def harga_termahal(self):
print("menghitung harga termahal...")
return max(self._daftar_harga.values())
katalog = Katalog({"buku": 95000, "laptop": 15000000})
print(katalog.harga_termahal)
print(katalog.harga_termahal)Output yang Dihasilkan:
menghitung harga termahal...
15000000
15000000Kalimat "menghitung harga termahal..." hanya tercetak satu kali meskipun property diakses dua kali — akses kedua membaca hasil tersimpan di instance. Gunakan cached_property untuk turunan data yang mahal dan jarang berubah, bukan untuk nilai yang bisa berubah selama program berjalan.
singledispatch: Pemilihan Implementasi Berdasarkan Tipe #
Decorator @singledispatch memisahkan logika per tipe argumen pertama. Kita daftarkan satu function umum lalu implementasi khusus per tipe:
from functools import singledispatch
@singledispatch
def tampilkan(nilai):
print(f"umum: {nilai!r}")
@tampilkan.register
def _(nilai: int):
print(f"integer: {nilai}")
@tampilkan.register
def _(nilai: str):
print(f"string: {nilai}")
tampilkan(42)
tampilkan("Budi")
tampilkan(3.14)Output yang Dihasilkan:
integer: 42
string: Budi
umum: 3.14Interpreter memanggil implementasi yang cocok dengan tipe argumen: 42 masuk ke versi int, "Budi" ke versi str, dan 3.14 yang tidak punya versi khusus jatuh ke function generik. Pola ini menggantikan rantai if isinstance(...) panjang dengan pendaftaran yang bisa disebar ke beberapa module.
Alat-alat functools berbagi satu tema besar: memindahkan pekerjaan yang berulang — menghitung ulang, mengulang argumen, mencocokkan tipe manual — keluar dari kode kita dan menyerahkannya ke interpreter Python.