メインコンテンツにスキップ
モジュール 9 · レッスン 4 / 4コース内の 36/36~14 min
モジュールのレッスン (4/4)

デコレータ: 関数を変更する関数

デコレータ は、別の関数を受け取り、 それの「装飾」バージョン。動作を追加する強力な方法です 本体には触れずに(ロギング、タイミング、キャッシュ、再試行、検証) 本来の機能。

値としての機能

Python では、関数はオブジェクトです。関数を渡したり、返したり、割り当てたりすることができます。

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

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

最小限のデコレータ

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

@decorator 構文

割り当て somma = log(somma) は、@ を使用してよりエレガントに記述されます。

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

まったく同じものです。上記の定義の @log は次と同等です。 「この関数が定義されたらすぐに、log(function) に置き換えてください。」

*args, **kwargs: 署名を受け入れます

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

*args は位置引数、**kwargs キーワード引数を収集します。彼らはさせます 任意の関数で動作するラッパーを作成します。

functools.wraps: メタデータを保持する

wraps がないと、装飾された関数は __name__、__doc__、ヒントを失います。

Python
from functools import wraps

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

例: @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()

標準ライブラリからのデコレータ

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 — 純粋な関数の結果をメモ化します。
  • @property — メソッドを属性として公開します (OOP モジュールで見られます)。
  • @staticmethod、@classmethod — self の受け渡し方法を変更します。

引数を持つデコレータ

デコレータは引数も受け入れることができます。これを実装するには、3 レベルのネストされた関数を作成する必要があります。最も外側の関数はデコレーターの引数を受け取り、中央の関数は装飾された関数を受け取り、最も内側の関数 (wrapper) は呼び出し中に装飾された関数に渡される実際の引数を受け取ります。

自分で試してみてください

運動#python.m9.l4.e1
試行回数: 0読み込み中…

装飾された関数を呼び出し、その結果 * 2 を返すデコレータ `double_result` を作成します。それを、x + 1 を返す関数 `f(x)` に適用します。`f(10)` を評価します。

エディターを読み込み中…
ヒントを表示

ラッパーは func を呼び出し、2 を乗算した結果を返します。

3 回の試行後に解決策が利用可能になります

復習演習

運動#python.m9.l4.e2
試行回数: 0読み込み中…

functools.lru_cache を使用して、再帰 fib(n) 関数を高速化します。 fib(30) を計算し、「r」に代入します。 「r」を評価します。

エディターを読み込み中…
ヒントを表示

def を超える @lru_cache(maxsize=None)。

3 回の試行後に解決策が利用可能になります

追加の課題

運動#python.m9.l4.e3
試行回数: 0読み込み中…

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)`.

エディターを読み込み中…
ヒントを表示

ラッパーは func(*args, **kwargs) を呼び出し、結果を 2 で乗算します。

3 回の試行後に解決策が利用可能になります