0Pricing
Python Academy · レッスン

カスタムデコレーターの作成

ラッパー関数と functools.wraps を使ってデコレーターを作成します。

「カスタムデコレーターの作成」はCoddyKit上の無料Python Academyレッスンです。 これはレッスン2/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはPython Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Python Academyコースには全4レッスンが含まれています。

導入

デコレーターは他の関数をラップし、元の関数を変更せずに前後へ動作を追加する関数です。

デコレーターとは

関数の上に置いた@decoratorは、func = decorator(func)という構文糖衣です。デコレーターは関数を受け取り、ラッパーを返します。
def shout(func):
    def wrapper(*a, **kw):
        result = func(*a, **kw)
        return str(result).upper()
    return wrapper
@shout
def greet(name): return f'hello {name}'
print(greet('Alice'))

ラッパーパターン

ラッパー関数は元の関数と同じシグネチャを持ちます。内部で元の関数を呼び出し、その前後に動作を追加します。
def timer(func):
    import time
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        print(f'{func.__name__} took {time.time()-start:.4f}s')
        return result
    return wrapper
@timer
def slow(): import time; time.sleep(0.01)
slow()

functools.wraps

ラッパーに@wraps(func)を付けると、元の関数の__name__、__doc__、__annotations__が保持されます。
from functools import wraps
def log(func):
    @wraps(func)
    def wrapper(*a, **kw):
        print(f'calling {func.__name__}')
        return func(*a, **kw)
    return wrapper
@log
def add(a, b): return a + b
print(add.__name__)
add(1, 2)

引数付きデコレーター

引数を持つデコレーターには、ラップをもう1段階追加する必要があります。引数を受け取り、デコレーターを返すファクトリ関数を用意します。
def repeat(n):
    def decorator(func):
        from functools import wraps
        @wraps(func)
        def wrapper(*a, **kw):
            for _ in range(n): func(*a, **kw)
        return wrapper
    return decorator
@repeat(3)
def hi(): print('hi')
hi()

デコレーターの重ね掛け

複数のデコレーターは下から上へ重ねられます。@d2 @d1はfunc = d2(d1(func))を意味します。外側(上側)のデコレーターが最後に実行されます。
def bold(f):
    from functools import wraps
    @wraps(f)
    def w(*a,**k): return '<b>' + f(*a,**k) + '</b>'
    return w
def italic(f):
    from functools import wraps
    @wraps(f)
    def w(*a,**k): return '<i>' + f(*a,**k) + '</i>'
    return w
@bold
@italic
def text(): return 'hello'
print(text())

クラスベースのデコレーター

__call__を持つクラスはデコレーターとして使用できます。呼び出し間で状態を保持できます。
class Counter:
    def __init__(self, func): self.func,self.count=func,0
    def __call__(self, *a, **kw):
        self.count+=1
        return self.func(*a, **kw)
@Counter
def greet(name): return f'hi {name}'
greet('Alice'); greet('Bob')
print(greet.count)

メモ化デコレーター

以前の結果をキャッシュして再計算を避けます。if args in cache: return cache[args]のように記述します。functools.lru_cacheにはこの機能が組み込まれています。
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(30))

認可デコレーター

デコレーターはアクセス制御によく使われます。関数を実行する前に、ユーザーがログインしているか確認できます。
def requires_auth(func):
    from functools import wraps
    @wraps(func)
    def wrapper(user, *a, **kw):
        if not user.get('authenticated'):
            raise PermissionError('Not authenticated')
        return func(user, *a, **kw)
    return wrapper
@requires_auth
def dashboard(user): return 'Welcome!'
try: dashboard({'authenticated': False})
except PermissionError as e: print(e)

再試行デコレーター

関数をラップして、失敗時に再試行できます。for _ in range(retries): try: return func() except Exception: passのように記述します。
import random
from functools import wraps
def retry(times=3):
    def dec(func):
        @wraps(func)
        def wrapper(*a, **kw):
            for i in range(times):
                try: return func(*a, **kw)
                except Exception as e:
                    if i==times-1: raise
        return wrapper
    return dec
@retry(3)
def flaky():
    if random.random()<0.7: raise ValueError
    return 'ok'
try: print(flaky())
except ValueError: print('failed')

デコレーターのベストプラクティス

必ず@functools.wrapsを使用してください。デコレーターは単一の目的に保ちます。追加する動作を文書化します。デコレーター自体は、デコレーターを適用した関数とは分けてテストしてください。
from functools import wraps
def validate_positive(func):
    @wraps(func)
    def wrapper(x, *a, **kw):
        if x <= 0: raise ValueError(f'{x} must be positive')
        return func(x, *a, **kw)
    return wrapper
@validate_positive
def sqrt(x): return x**0.5
print(sqrt(9))

クイックチェック

ラッパーに@functools.wraps(func)を適用すると、何が起こりますか?

復習

デコレーター:func = decorator(func)です。wrapper(*args, **kwargs)を使います。@wrapsはメタデータを保持します。引数付きデコレーターでは、ラップがもう1段階必要です。複数の@decoratorを重ねて使えます。

学習を続ける

素晴らしい進み具合です。次の概念を身につけるために、このまま学習を続けてください。

よくある質問

「カスタムデコレーターの作成」レッスンは無料ですか?

はい。「カスタムデコレーターの作成」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Python Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Python Academyコースには全4レッスンが含まれています。

「カスタムデコレーターの作成」で何を学びますか?

ラッパー関数と functools.wraps を使ってデコレーターを作成します。 ブラウザで直接実行するハンズオンコードでPython Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Python Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのPython Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン2/4です。

「カスタムデコレーターの作成」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このPython Academyレッスンでコードを書いて実行できますか?

はい。すべてのPython Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. 第一級オブジェクトとしての関数
  2. カスタムデコレーターの作成
  3. @property デコレーター
  4. @classmethod と @staticmethod
← Python Academyに戻る