Lompati ke konten utama
Modul 9 · Pelajaran 4 dari 436/36 dalam kursus~14 min
Pelajaran modul (4/4)

Dekorator: fungsi yang mengubah fungsi

dekorator adalah fungsi yang mengambil fungsi lain dan mengembalikan a versi "dihiasi" itu. Ini adalah cara ampuh untuk menambahkan perilaku (logging, timing, caching, coba lagi, validasi) tanpa menyentuh isi fungsi aslinya.

Berfungsi sebagai nilai

Dalam Python, fungsi adalah objek: Anda dapat meneruskannya, mengembalikannya, menugaskannya.

Python
def saluta():
    print("ciao")

f = saluta   # NIENTE parentesi: assegno la funzione
f()          # 'ciao'

Dekorator minimal

Python
def log(func):
    def wrapper(*args, **kwargs):
        print(f"chiamo {func.__name__}")
        risultato = func(*args, **kwargs)
        print(f"ho finito {func.__name__}")
        return risultato
    return wrapper

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

somma = log(somma)   # ora somma è "wrapper"
somma(2, 3)
# chiamo somma
# ho finito somma
# 5

Sintaks @decorator

Tugas somma = log(somma) ditulis lebih elegan dengan @:

Python
@log
def somma(a, b):
    return a + b

Ini hal yang persis sama. @log definisi di atas setara dengan: "segera setelah fungsi ini ditentukan, gantilah dengan log(function)".

*args, **kwargs: menerima tanda tangan apa pun

Python
def wrapper(*args, **kwargs):
    return func(*args, **kwargs)

*args mengumpulkan argumen posisi, **kwargs argumen kata kunci. Mereka membiarkan Anda menulis pembungkus yang berfungsi dengan fungsi apa pun.

functools.wraps: melestarikan metadata

Tanpa wraps, fungsi yang didekorasi kehilangan __name__, __doc__, petunjuk:

Python
from functools import wraps

def log(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        ...
        return func(*args, **kwargs)
    return wrapper

Contoh: @timeit

Python
from functools import wraps
import time

def timeit(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        t0 = time.perf_counter()
        risultato = func(*args, **kwargs)
        dt = time.perf_counter() - t0
        print(f"{func.__name__} ha impiegato {dt*1000:.2f} ms")
        return risultato
    return wrapper

@timeit
def somma_grande():
    return sum(range(10**6))

somma_grande()

Dekorator dari stdlib

Python
from functools import lru_cache

@lru_cache(maxsize=None)
def fib(n):
    if n < 2: return n
    return fib(n - 1) + fib(n - 2)

fib(100)  # istantaneo grazie alla cache
  • @lru_cache — mengingat hasil fungsi murni.
  • @property — menampilkan metode sebagai atribut (terlihat di modul OOP).
  • @staticmethod, @classmethod — mengubah cara self diteruskan.

Dekorator dengan argumen

Dekorator juga dapat menerima argumen. Untuk mengimplementasikannya, Anda harus menulis tiga tingkat fungsi bertingkat: fungsi terluar menerima argumen dekorator, fungsi tengah menerima fungsi yang dihias, dan fungsi terdalam (wrapper) menerima argumen aktual yang diteruskan ke fungsi yang dihias selama panggilan.

Cobalah sendiri

Latihan#python.m9.l4.e1
Upaya: 0Memuat…

Tulis dekorator `double_result` yang memanggil fungsi yang dihias dan mengembalikan hasilnya * 2. Terapkan ke fungsi `f(x)` yang mengembalikan x + 1. Evaluasi `f(10)`.

Memuat editor…
Tunjukkan petunjuk

Pembungkus memanggil func dan mengembalikan hasil dikalikan 2.

Solusi tersedia setelah 3 upaya

Tinjau latihan

Latihan#python.m9.l4.e2
Upaya: 0Memuat…

Gunakan functools.lru_cache untuk mempercepat fungsi fib(n) rekursif. Hitung fib(30) dan tetapkan ke `r`. Evaluasi `r`.

Memuat editor…
Tunjukkan petunjuk

@lru_cache(maxsize=None) di atas def.

Solusi tersedia setelah 3 upaya

Tantangan tambahan

Latihan#python.m9.l4.e3
Upaya: 0Memuat…

Define a decorator `double_result` that doubles (multiplies by 2) the return value of the decorated function. Apply the decorator to the function `def sum_nums(a, b): return a + b`. Finally, evaluate `sum_nums(3, 4)`.

Memuat editor…
Tunjukkan petunjuk

Pembungkus memanggil func(*args, **kwargs) dan mengalikan hasilnya dengan 2.

Solusi tersedia setelah 3 upaya