Python

Python Decorators: Fungsi Pembungkus

Pelajari function decorators, class decorators, built-in decorators, custom decorators, dan parameterized decorators dengan contoh kode praktis

1. Pengenalan Decorators

Decorator adalah fungsi yang menerima fungsi lain sebagai argumen dan mengembalikan fungsi baru yang biasanya diperluas atau dimodifikasi perilakunya. Dengan decorator, Anda bisa menambahkan fungsionalitas ekstra pada fungsi tanpa mengubah kode aslinya.

Konsep decorator sejajar dengan prinsip Open/Closed Principle dalam pemrograman — kode terbuka untuk diperluas, tetapi tertutup untuk dimodifikasi. Decorator menggunakan sintaks @decorator_name yang ditempatkan di atas fungsi.

Mengapa Decorator Penting?

Fitur Penjelasan
Code ReusabilityTulis logika sekali, gunakan di banyak fungsi
Separation of ConcernsPisahkan logika bisnis dari logika tambahan (logging, caching, dll)
DRY PrincipleHindari duplikasi kode yang sama
Clean CodeKode utama tetap bersih dan fokus pada logika inti
Framework PatternsDigunakan luas di Flask, Django, FastAPI, pytest, dll
Diagram: Cara Kerja Decorator
┌─────────────────────────────────────────────────────┐
│              CARA KERJA DECORATOR                    │
│                                                     │
│  @my_decorator          ┌───────────────────┐       │
│  def say_hello():       │  my_decorator     │       │
│      print("Hello")     │  ┌─────────────┐  │       │
│                         │  │ say_hello() │  │       │
│  # Sama dengan:         │  │ (original)  │  │       │
│  # say_hello =          │  └──────┬──────┘  │       │
│  #   my_decorator(      │         │         │       │
│  #     say_hello        │  ┌──────▼──────┐  │       │
│  #   )                  │  │ wrapper()   │──┼──►    │
│                         │  │ (enhanced)  │  │ output│
│                         │  └─────────────┘  │       │
│                         └───────────────────┘       │
└─────────────────────────────────────────────────────┘

2. Fungsi sebagai First-Class Citizen

Sebelum memahami decorator, kita perlu memahami bahwa di Python, fungsi adalah first-class citizen — fungsi bisa disimpan dalam variabel, diteruskan sebagai argumen, dan dikembalikan dari fungsi lain.

Python — First-Class Functions
# Fungsi bisa disimpan dalam variabel
def sapa():
    return "Halo, dunia!"

fungsi_saya = sapa  # TANPA tanda kurung — menyimpan referensi
print(fungsi_saya())  # Halo, dunia!

# Fungsi bisa diteruskan sebagai argumen
def jalankan(fungsi):
    print("Menjalankan fungsi...")
    hasil = fungsi()
    print(f"Hasil: {hasil}")

jalankan(sapa)
# Menjalankan fungsi...
# Hasil: Halo, dunia!

# Fungsi bisa mengembalikan fungsi lain
def buat_pengali(n):
    def pengali(x):
        return x * n
    return pengali

kali_dua = buat_pengali(2)
kali_tiga = buat_pengali(3)

print(kali_dua(5))   # 10
print(kali_tiga(5))   # 15
print(kali_dua(10))   # 20

# Fungsi bisa disimpan dalam data structure
def tambah(a, b): return a + b
def kurang(a, b): return a - b
def kali(a, b):   return a * b

operasi = {
    "+": tambah,
    "-": kurang,
    "*": kali
}

print(operasi["+"](10, 5))  # 15
print(operasi["*"](10, 5))  # 50

# Inner function (fungsi bersarang)
def luar():
    pesan = "Ini dari fungsi luar"

    def dalam():
        print(pesan)  # Bisa akses variabel dari fungsi luar

    return dalam()

luar()  # Ini dari fungsi luar

Closures

Python — Closures
# Closure: inner function yang mengingat variabel dari enclosing scope
def buat_counter():
    count = 0

    def counter():
        nonlocal count
        count += 1
        return count

    return counter

hitung = buat_counter()
print(hitung())  # 1
print(hitung())  # 2
print(hitung())  # 3

# Closure untuk membuat decorator nanti
def logger(func):
    def wrapper(*args, **kwargs):
        print(f"Memanggil {func.__name__}...")
        hasil = func(*args, **kwargs)
        print(f"Selesai.")
        return hasil
    return wrapper

def tambah(a, b):
    return a + b

tambah_dengan_log = logger(tambah)
print(tambah_dengan_log(3, 5))
# Memanggil tambah...
# Selesai.
# 8

3. Function Decorator

Function decorator adalah fungsi yang membungkus fungsi lain untuk menambah atau memodifikasi perilakunya. Ini adalah jenis decorator yang paling umum digunakan.

Membuat Decorator Pertama

Python — Decorator Pertama
# === Membuat decorator sederhana ===

def dekorator_sederhana(func):
    def wrapper():
        print("=== Sebelum fungsi dipanggil ===")
        func()
        print("=== Setelah fungsi dipanggil ===")
    return wrapper

# Menggunakan decorator dengan sintaks @
@dekorator_sederhana
def sapa():
    print("Halo, dunia!")

sapa()
# Output:
# === Sebelum fungsi dipanggil ===
# Halo, dunia!
# === Setelah fungsi dipanggil ===

# Tanpa sintaks @, sama dengan:
# def sapa():
#     print("Halo, dunia!")
# sapa = dekorator_sederhana(sapa)

Decorator dengan *args dan **kwargs

Python — Args & Kwargs
# Decorator yang bisa membungkus fungsi dengan argumen apapun
def universal_decorator(func):
    def wrapper(*args, **kwargs):
        print(f"Memanggil {func.__name__} dengan args={args}, kwargs={kwargs}")
        hasil = func(*args, **kwargs)
        print(f"{func.__name__} mengembalikan: {hasil}")
        return hasil
    return wrapper

@universal_decorator
def tambah(a, b):
    return a + b

@universal_decorator
def sapa(nama, pesan="Halo"):
    return f"{pesan}, {nama}!"

print(tambah(3, 5))
# Memanggil tambah dengan args=(3, 5), kwargs={}
# tambah mengembalikan: 8
# 8

print(sapa("Budi", pesan="Selamat pagi"))
# Memanggil sapa dengan args=('Budi',), kwargs={'pesan': 'Selamat pagi'}
# sapa mengembalikan: Selamat pagi, Budi!
# Selamat pagi, Budi!

Preserving Metadata dengan @functools.wraps

Python — @functools.wraps
import functools

# TANPA functools.wraps — metadata hilang!
def decorator_tanpa_wraps(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@decorator_tanpa_wraps
def fungsi_contoh():
    """Ini docstring fungsi contoh."""
    pass

print(fungsi_contoh.__name__)   # wrapper ← salah!
print(fungsi_contoh.__doc__)    # None    ← hilang!

# DENGAN functools.wraps — metadata terjaga!
def decorator_dengan_wraps(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@decorator_dengan_wraps
def fungsi_contoh2():
    """Ini docstring fungsi contoh."""
    pass

print(fungsi_contoh2.__name__)  # fungsi_contoh2 ← benar!
print(fungsi_contoh2.__doc__)   # Ini docstring fungsi contoh. ← terjaga!

# SELALU gunakan @functools.wraps(func) di decorator Anda!
⚠️ Best Practice

Selalu gunakan @functools.wraps(func) di dalam decorator Anda. Ini mempertahankan metadata asli fungsi seperti __name__, __doc__, dan __module__ yang penting untuk debugging dan introspeksi.

4. Built-in Decorators

Python menyediakan beberapa decorator bawaan yang sangat berguna untuk penggunaan sehari-hari.

@staticmethod, @classmethod, @property

Python — Built-in Decorators
# === @staticmethod ===
# Metode yang tidak membutuhkan akses ke instance atau class
class Kalkulator:
    @staticmethod
    def tambah(a, b):
        return a + b

    @staticmethod
    def kali(a, b):
        return a * b

# Bisa dipanggil tanpa membuat instance
print(Kalkulator.tambah(5, 3))   # 8
print(Kalkulator.kali(4, 6))     # 24

# === @classmethod ===
# Metode yang menerima class sebagai argumen pertama (cls)
class Mahasiswa:
    jumlah = 0

    def __init__(self, nama, nim):
        self.nama = nama
        self.nim = nim
        Mahasiswa.jumlah += 1

    @classmethod
    def dari_string(cls, data_str):
        """Factory method: buat Mahasiswa dari string"""
        nama, nim = data_str.split(",")
        return cls(nama.strip(), nim.strip())

    @classmethod
    def get_jumlah(cls):
        return cls.jumlah

# Membuat instance dengan factory method
mhs1 = Mahasiswa("Budi", "2024001")
mhs2 = Mahasiswa.dari_string("Ani, 2024002")
print(Mahasiswa.get_jumlah())  # 2

# === @property ===
# Mengubah method menjadi attribute yang bisa diakses seperti variabel
class Lingkaran:
    def __init__(self, jari_jari):
        self._jari_jari = jari_jari

    @property
    def jari_jari(self):
        """Getter untuk jari_jari"""
        return self._jari_jari

    @jari_jari.setter
    def jari_jari(self, nilai):
        """Setter dengan validasi"""
        if nilai <= 0:
            raise ValueError("Jari-jari harus positif!")
        self._jari_jari = nilai

    @property
    def luas(self):
        """Hitung luas — computed property"""
        import math
        return math.pi * self._jari_jari ** 2

    @property
    def keliling(self):
        """Hitung keliling — computed property"""
        import math
        return 2 * math.pi * self._jari_jari

lingkaran = Lingkaran(5)
print(f"Jari-jari: {lingkaran.jari_jari}")    # 5
print(f"Luas: {lingkaran.luas:.2f}")          # 78.54
print(f"Keliling: {lingkaran.keliling:.2f}")  # 31.42

lingkaran.jari_jari = 10  # Menggunakan setter
print(f"Luas baru: {lingkaran.luas:.2f}")     # 314.16

@abstractmethod dan Decorator Lainnya

Python — Abstract & Lainnya
# === @abstractmethod ===
from abc import ABC, abstractmethod

class Hewan(ABC):
    @abstractmethod
    def suara(self):
        """Setiap hewan harus punya suara"""
        pass

    @abstractmethod
    def bergerak(self):
        pass

class Kucing(Hewan):
    def suara(self):
        return "Meow!"

    def bergerak(self):
        return "Kucing berjalan dengan 4 kaki"

class Burung(Hewan):
    def suara(self):
        return "Cuit!"

    def bergerak(self):
        return "Burung terbang di udara"

kucing = Kucing()
print(kucing.suara())     # Meow!
print(kucing.bergerak())  # Kucing berjalan dengan 4 kaki

# hewan = Hewan()  ← Error! Tidak bisa instantiate abstract class

# === @functools.lru_cache ===
import functools

@functools.lru_cache(maxsize=128)
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(50))   # 12586269025 — sangat cepat berkat caching!
print(fibonacci.cache_info())
# CacheInfo(hits=48, misses=51, maxsize=128, currsize=51)

# === @dataclasses.dataclass ===
from dataclasses import dataclass

@dataclass
class Titik:
    x: float
    y: float

    def jarak_ke(self, lain):
        return ((self.x - lain.x)**2 + (self.y - lain.y)**2) ** 0.5

t1 = Titik(3, 4)
t2 = Titik(0, 0)
print(t1)                # Titik(x=3, y=4)
print(t1.jarak_ke(t2))   # 5.0

5. Custom Decorators

Timer Decorator

Python — Timer Decorator
import functools
import time

def timer(func):
    """Decorator untuk mengukur waktu eksekusi fungsi"""
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        mulai = time.perf_counter()
        hasil = func(*args, **kwargs)
        selesai = time.perf_counter()
        print(f"⏱️ {func.__name__} selesai dalam {selesai - mulai:.4f} detik")
        return hasil
    return wrapper

@timer
def proses_data(n):
    """Fungsi simulasi pemrosesan data"""
    total = sum(i ** 2 for i in range(n))
    return total

hasil = proses_data(1000000)
print(f"Hasil: {hasil}")
# ⏱️ proses_data selesai dalam 0.0823 detik
# Hasil: 333332833333500000

Logger Decorator

Python — Logger Decorator
import functools
from datetime import datetime

def logger(func):
    """Decorator untuk logging pemanggilan fungsi"""
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        waktu = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        print(f"[{waktu}] ▶️ Memanggil {func.__name__}()")
        print(f"  args: {args}")
        print(f"  kwargs: {kwargs}")

        try:
            hasil = func(*args, **kwargs)
            print(f"[{waktu}] ✅ {func.__name__}() → {hasil}")
            return hasil
        except Exception as e:
            print(f"[{waktu}] ❌ {func.__name__}() → Error: {e}")
            raise

    return wrapper

@logger
def bagi(a, b):
    return a / b

bagi(10, 3)
# [2026-06-26 10:30:00] ▶️ Memanggil bagi()
#   args: (10, 3)
#   kwargs: {}
# [2026-06-26 10:30:00] ✅ bagi() → 3.3333333333333335

# bagi(10, 0)  ← Akan log error juga!

Retry Decorator

Python — Retry Decorator
import functools
import time

def retry(max_attempts=3, delay=1):
    """Decorator untuk mencoba ulang fungsi jika gagal"""
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(1, max_attempts + 1):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    print(f"⚠️ Percobaan {attempt}/{max_attempts} gagal: {e}")
                    if attempt < max_attempts:
                        print(f"   Menunggu {delay} detik...")
                        time.sleep(delay)
            raise Exception(f"Gagal setelah {max_attempts} percobaan")
        return wrapper
    return decorator

# Contoh penggunaan
import random

@retry(max_attempts=3, delay=0.5)
def ambil_data_api():
    """Simulasi API call yang kadang gagal"""
    if random.random() < 0.7:  # 70% kemungkinan gagal
        raise ConnectionError("Server tidak merespons")
    return {"status": "ok", "data": [1, 2, 3]}

try:
    hasil = ambil_data_api()
    print(f"Data: {hasil}")
except Exception as e:
    print(f"Error final: {e}")

6. Parameterized Decorators

Parameterized decorator adalah decorator yang bisa menerima argumen. Bentuknya adalah decorator factory — fungsi yang mengembalikan decorator.

Python — Parameterized Decorators
import functools

# Parameterized decorator = decorator factory
# 3 tingkat nested function!

def repeat(n):
    """Menjalankan fungsi sebanyak n kali"""
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            hasil = None
            for _ in range(n):
                hasil = func(*args, **kwargs)
            return hasil
        return wrapper
    return decorator

@repeat(3)
def sapa(nama):
    print(f"Halo, {nama}!")

sapa("Budi")
# Halo, Budi!
# Halo, Budi!
# Halo, Budi!

# === Access Control Decorator ===
def require_role(role):
    """Membatasi akses berdasarkan role"""
    def decorator(func):
        @functools.wraps(func)
        def wrapper(user, *args, **kwargs):
            if user.get("role") != role:
                print(f"❌ Akses ditolak! Butuh role: {role}")
                return None
            return func(user, *args, **kwargs)
        return wrapper
    return decorator

@require_role("admin")
def hapus_user(user, target):
    print(f"✅ {user['nama']} menghapus user: {target}")
    return True

@require_role("admin")
def lihat_laporan(user):
    print(f"✅ {user['nama']} melihat laporan")
    return {"total": 100}

admin = {"nama": "Budi", "role": "admin"}
viewer = {"nama": "Ani", "role": "viewer"}

hapus_user(admin, "Dimas")    # ✅ Budi menghapus user: Dimas
hapus_user(viewer, "Dimas")   # ❌ Akses ditolak! Butuh role: admin
lihat_laporan(admin)          # ✅ Budi melihat laporan

Rate Limiter Decorator

Python — Rate Limiter
import functools
import time
from collections import defaultdict

def rate_limit(max_calls, period):
    """Membatasi jumlah pemanggilan fungsi dalam periode waktu"""
    def decorator(func):
        calls = []

        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            sekarang = time.time()
            # Hapus panggilan lama
            calls[:] = [t for t in calls if sekarang - t < period]

            if len(calls) >= max_calls:
                tunggu = period - (sekarang - calls[0])
                print(f"⚠️ Rate limit! Tunggu {tunggu:.1f} detik")
                return None

            calls.append(sekarang)
            return func(*args, **kwargs)
        return wrapper
    return decorator

@rate_limit(max_calls=3, period=5)
def kirim_pesan(pesan):
    print(f"📨 Mengirim: {pesan}")
    return True

# 3 panggilan pertama berhasil
kirim_pesan("Pesan 1")  # 📨 Mengirim: Pesan 1
kirim_pesan("Pesan 2")  # 📨 Mengirim: Pesan 2
kirim_pesan("Pesan 3")  # 📨 Mengirim: Pesan 3
kirim_pesan("Pesan 4")  # ⚠️ Rate limit! Tunggu X.X detik

# === Max Length Decorator ===
def max_length(limit):
    """Membatasi panjang input string"""
    def decorator(func):
        @functools.wraps(func)
        def wrapper(text, *args, **kwargs):
            if len(text) > limit:
                text = text[:limit] + "..."
            return func(text, *args, **kwargs)
        return wrapper
    return decorator

@max_length(20)
def tampilkan_judul(judul):
    print(f"📰 {judul}")

tampilkan_judul("Judul yang sangat panjang sekali dan melebihi batas")
# 📰 Judul yang sangat panja...

7. Class Decorator

Class bisa berperan sebagai decorator. Ini berguna ketika Anda membutuhkan state (status) dalam decorator, yang sulit dilakukan dengan fungsi closure biasa.

Python — Class as Decorator
import functools
import time

# Class sebagai decorator (menggunakan __call__)
class Timer:
    """Class-based decorator untuk mengukur waktu"""
    def __init__(self, func):
        functools.update_wrapper(self, func)
        self.func = func
        self.total_time = 0
        self.call_count = 0

    def __call__(self, *args, **kwargs):
        mulai = time.perf_counter()
        hasil = self.func(*args, **kwargs)
        durasi = time.perf_counter() - mulai
        self.total_time += durasi
        self.call_count += 1
        print(f"⏱️ {self.func.__name__}: {durasi:.4f}s (call #{self.call_count})")
        return hasil

    def statistik(self):
        rata = self.total_time / self.call_count if self.call_count else 0
        print(f"📊 {self.func.__name__}: {self.call_count} calls, "
              f"total={self.total_time:.4f}s, avg={rata:.4f}s")

@Timer
def proses_a(n):
    return sum(range(n))

@Timer
def proses_b(n):
    return [i**2 for i in range(n)]

proses_a(1000000)   # ⏱️ proses_a: 0.0312s (call #1)
proses_a(500000)    # ⏱️ proses_a: 0.0156s (call #2)
proses_b(1000000)   # ⏱️ proses_b: 0.0891s (call #1)

proses_a.statistik()  # 📊 proses_a: 2 calls, total=0.0468s, avg=0.0234s

# === Call Counter ===
class CallCounter:
    """Menghitung berapa kali fungsi dipanggil"""
    def __init__(self, func):
        functools.update_wrapper(self, func)
        self.func = func
        self.count = 0
        self.history = []

    def __call__(self, *args, **kwargs):
        self.count += 1
        self.history.append(args)
        return self.func(*args, **kwargs)

    def reset(self):
        self.count = 0
        self.history = []

@CallCounter
def tambah(a, b):
    return a + b

print(tambah(1, 2))   # 3
print(tambah(3, 4))   # 7
print(tambah(5, 6))   # 11
print(f"Dipanggil {tambah.count} kali")  # Dipanggil 3 kali
print(f"History: {tambah.history}")       # [(1,2), (3,4), (5,6)]

# === Memoization Class ===
class Memoize:
    """Caching hasil fungsi"""
    def __init__(self, func):
        functools.update_wrapper(self, func)
        self.func = func
        self.cache = {}

    def __call__(self, *args):
        if args not in self.cache:
            self.cache[args] = self.func(*args)
            print(f"💾 Cache MISS: {args} → {self.cache[args]}")
        else:
            print(f"⚡ Cache HIT: {args} → {self.cache[args]}")
        return self.cache[args]

@Memoize
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(10))  # 55 — dengan caching!

8. Decorator Chaining

Anda bisa menerapkan beberapa decorator pada satu fungsi sekaligus. Decorator diterapkan dari bawah ke atas (bottom-up).

Python — Chaining
import functools

def bold(func):
    """Membungkus output dengan tag bold"""
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        return f"<b>{func(*args, **kwargs)}</b>"
    return wrapper

def italic(func):
    """Membungkus output dengan tag italic"""
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        return f"<i>{func(*args, **kwargs)}</i>"
    return wrapper

def uppercase(func):
    """Mengubah output menjadi huruf besar"""
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs).upper()
    return wrapper

# Chaining: diterapkan dari bawah ke atas
@bold
@italic
@uppercase
def sapa(nama):
    return f"halo, {nama}"

print(sapa("budi"))
# Eksekusi: sapa → uppercase → italic → bold
# <b><i>HALO, BUDI</i></b>

# Sama dengan:
# sapa = bold(italic(uppercase(sapa)))

# Contoh chaining yang lebih praktis
def validate_positive(func):
    @functools.wraps(func)
    def wrapper(x, *args, **kwargs):
        if x < 0:
            raise ValueError(f"Input harus positif, dapat: {x}")
        return func(x, *args, **kwargs)
    return wrapper

def log_call(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f"📞 Memanggil {func.__name__}({args})")
        return func(*args, **kwargs)
    return wrapper

@log_call
@validate_positive
def akar_kuadrat(x):
    return x ** 0.5

print(akar_kuadrat(16))  # 4.0
# 📞 Memanggil akar_kuadrat((16,))
# 4.0

# akar_kuadrat(-5)  ← ValueError: Input harus positif

9. Studi Kasus Praktis

Sistem Autentikasi Sederhana

Python — Studi Kasus
import functools

# === Decorator Collection ===
def login_required(func):
    """Memastikan user sudah login"""
    @functools.wraps(func)
    def wrapper(user, *args, **kwargs):
        if not user.get("is_authenticated"):
            print("❌ Silakan login terlebih dahulu!")
            return None
        return func(user, *args, **kwargs)
    return wrapper

def admin_only(func):
    """Memastikan user adalah admin"""
    @functools.wraps(func)
    def wrapper(user, *args, **kwargs):
        if user.get("role") != "admin":
            print("❌ Hanya admin yang bisa mengakses!")
            return None
        return func(user, *args, **kwargs)
    return wrapper

def cache_result(func):
    """Cache hasil fungsi berdasarkan argumen"""
    cache = {}

    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        key = str(args) + str(kwargs)
        if key not in cache:
            cache[key] = func(*args, **kwargs)
            print(f"💾 Cache disimpan untuk key: {key[:30]}...")
        else:
            print(f"⚡ Menggunakan cache untuk key: {key[:30]}...")
        return cache[key]
    return wrapper

# === Menggunakan decorator ===
@login_required
def lihat_profil(user):
    return f"Profil: {user['nama']} ({user['email']})"

@login_required
@admin_only
def hapus_artikel(user, artikel_id):
    print(f"🗑️ Artikel #{artikel_id} dihapus oleh {user['nama']}")
    return True

@cache_result
def ambil_data_berat(query):
    """Simulasi query database yang lambat"""
    import time
    time.sleep(1)  # Simulasi delay
    return {"query": query, "results": [1, 2, 3]}

# Test
user_guest = {"nama": "Guest"}
user_admin = {
    "nama": "Budi",
    "email": "budi@email.com",
    "role": "admin",
    "is_authenticated": True
}

lihat_profil(user_guest)   # ❌ Silakan login terlebih dahulu!
lihat_profil(user_admin)   # Profil: Budi (budi@email.com)

hapus_artikel(user_admin, 42)  # 🗑️ Artikel #42 dihapus oleh Budi

ambil_data_berat("Python")  # 💾 Cache disimpan...
ambil_data_berat("Python")  # ⚡ Menggunakan cache...

10. Quiz: Uji Pemahamanmu!

Setelah membaca tutorial di atas, jawablah 5 pertanyaan berikut untuk menguji pemahamanmu tentang Decorators:

Pertanyaan 1: Apa itu decorator dalam Python?

a) Fungsi yang menerima class sebagai argumen
b) Fungsi yang menerima fungsi lain dan mengembalikan fungsi baru
c) Variabel yang menyimpan fungsi
d) Module untuk menghias tampilan

Pertanyaan 2: Apa fungsi dari @functools.wraps dalam decorator?

a) Membuat decorator lebih cepat
b) Mempercantik tampilan kode
c) Mempertahankan metadata asli fungsi (nama, docstring)
d) Mengenkripsi fungsi

Pertanyaan 3: Berapa lapisan nested function yang dibutuhkan parameterized decorator?

a) 1 lapis
b) 2 lapis
c) 3 lapis
d) 4 lapis

Pertanyaan 4: Ketika beberapa decorator di-chain, urutan eksekusinya dari mana?

a) Atas ke bawah
b) Bawah ke atas
c) Acak
d) Paralel

Pertanyaan 5: Method apa yang harus dimiliki class agar bisa digunakan sebagai decorator?

a) __init__
b) __str__
c) __call__
d) __repr__
🔍 Zoom
100%
🎨 Tema