連載
» 2019年11月26日 05時00分 公開

Python入門:[Python入門]デコレーターの基礎 (1/2)

デコレーターを使うと、関数に何らかの追加機能を持たせられる。デコレーターの定義や、その使い方、同じことをするPythonコードについて見ていこう。

[かわさきしんじ,Deep Insider編集部]

この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。

「Python入門」のインデックス

連載目次

 今回はPythonが提供するデコレーターの基礎について見ていく。

デコレーターとは

 本連載の第15回「ローカル関数とラムダ式」では関数を受け取る関数や、関数を戻り値とする関数を取り上げた。今回紹介するデコレーターとは、基本的には関数を受け取り、関数を戻り値とする関数のことだ(そうではない場合もある。後述)。クラスを受け取り、クラスを戻り値とする関数やクラスも書けるが、本稿では関数を受け取り、関数を戻り値とする基本的なデコレーターだけを紹介する。

 「関数を受け取り、関数を戻り値とする関数」というと難しく感じるかもしれないが、デコレーターの使用例は第29回「クラス変数/クラスメソッド/スタティックメソッド」の「クラスメソッド」で既に紹介している。デコレーターを使うだけなら、以下のように「@デコレーター」(デコレーター式)を使って関数を修飾(デコレート)するだけでよい(以下の例はメソッドだが、使い方は同じ)。

class MyClass:
    # …… 省略 ……

    @classmethod  # クラスメソッドであることを示すデコレーター
    def get_count(cls):  # デコレーターで修飾されたメソッド(関数)
        print(cls.count)

@classmethodデコレーターの使用例

 上のコードは、関数を受け取り、関数を戻りとする「classmethod」という名前の組み込み関数にその直下で定義しているget_count関数を渡していると考えられる。classmethod関数は、受け取ったget_count関数(メソッド)をクラスメソッドに変換するための処理をしてくれる。

 ここまでの話をまとめると次のようになる。

  • デコレーターとは、関数を受け取り、関数を戻り値とする関数のこと
  • デコレーターを使うには、関数定義(やクラス定義)の前に「@デコレーター」を付ける(修飾する)
  • デコレーターは、受け取った関数に何らかの処理を施して、それを戻り値とする

簡単なデコレーターの定義

 簡単な例を以下に示す。

def mydecorator(func):
    def inner_func(*args):
        print(f'before execute {func.__name__}')
        result = func(*args)
        print(f'after execute {func.__name__}')
        return result
    return inner_func

関数を受け取り関数を返す関数(デコレーター)

 mydecorator関数はパラメーターfuncに関数を受け取る。そして、内部で定義しているinner_func関数を戻り値とする。そのため、これはデコレーターとして使用できる。

 では、inner_func関数では何をしているかについて考えよう。まず、この関数はパラメーター*argsに任意の数の値を受け取る。そして、「result = func(*args)」という行で、自らのパラメーター*argsに受け取った値を引数として、mydecorator関数がパラメーターfuncに受け取った関数を呼び出している。その前後では、メッセージを表示している(「func.__name__」というのは、パラメーターfuncに受け取った関数の名前だ)。

 つまり、mydecorator関数は「受け取った関数を実行し、その前後にはメッセージを表示する関数」を戻り値とする。実際にこれを使ってみよう。まずはデコレーター式を使わずに、そのまま実行してみよう。

mylen = mydecorator(len)  # 組み込みのlen関数をmydecorator関数に渡す
print(mylen('string'))

mydecorator関数の使用例

 1行目では、変数mylenには、len関数を内部で実行(し、その前後にメッセージも表示)する関数が代入される。2行目では実際にその関数を呼び出している。「mylen('string')」は意味的には「inner_func('string')」である点に注意しよう(このとき、inner_func関数では、そのローカル変数funcに組み込みのlen関数が代入されている。これを確認したい人は、inner_func関数定義の本体の行目に「print(locals())」と書いてみよう)。

 実行すると、次のようになる。

実行結果 実行結果

 メッセージが表示され、文字列'string'の文字数が返されたので、その結果である「6」が表示された。

 なお、「mydecorator(len)」呼び出しの結果が関数(関数オブジェクト)になるので、上のコードは次のように書いてもよい(分かりやすいように、print関数呼び出しは省略)。

mydecorator(len)('string')

mydecorator(len)の戻り値は関数なので、「戻り値の関数('string')」という関数呼び出しが行われる

 今度はmydecorator関数を適用する関数も自分で作ってみよう。

def myfunc(msg):
    print(msg)

myfunc = mydecorator(myfunc)
myfunc('hello world')

自作のmyfunc関数にmydecorator関数を適用する

 実行結果を以下に示す。

実行結果 実行結果

 上のコードでは、myfunc関数を定義して、それをmydecorator関数に渡した結果を、同じ「myfunc」という変数(名前)に代入(束縛)している。これを簡略化して表記するのが、「@デコレーター」(デコレーター式)で使用した関数定義の修飾だ。つまり、「@デコレーター」を使うと、上のコードは次のように書ける。

@mydecorator
def myfunc(msg):
    print(msg)

デコレーター式で修飾した関数定義

 つまり、「@デコレーター」で関数を修飾するのは、上で見た「変数 = デコレーター(関数)」という記述と意味的には同じということだ。

複数のデコレーターによる関数定義の修飾

 次にデコレーターを2つ定義して、それらの使い方と意味するところについて考えてみよう。

def decorator1(func):
    def inner_func(*args):
        print('decorator 1')
        result = func(*args)
    return inner_func

def decorator2(func):
    def inner_func(*args):
        print('decorator 2')
        result = func(*args)
    return inner_func

2つのデコレーター

 デコレーターを複数適用するときの動作を調べることを目的とするので、ここでは2つのデコレーターは表示するメッセージが違うだけのものとしている。これらのデコレーターを使って、関数定義を修飾してみよう。

@decorator1
@decorator2
def myfunc(msg):
    print(msg)

2つのデコレーターで修飾されたmyfunc関数の定義

 このようにして定義したmyfunc関数を呼び出すと、どんな結果になるだろうか。つまり、2つのメッセージはどんな順序で表示されるだろうか。それが分かれば、この記述に対応する、デコレーター式表記を使わないPythonコードが分かる。

myfunc('hello world')

「decorator 1」と「decorator 2」の出力順はどうなるか

 実行結果は次のようになる。

実行結果 実行結果

 読者の予想通りか、そうでないかは分からないが、「decorator 1」が最初に表示されて、次に「decorator 2」が表示され、最後にmyfuncのコードが実行されていることが分かった。

 では、これに対応するデコレーター式を使わない表記はどうなるだろう。デコレーターで修飾をしていない同内容の関数を定義して、それらに順番にデコレーター関数を適用していってみよう。デコレーターが2つなので、それらが適用される順番は2通りだ。

def yourfunc(msg):
    print(msg)

tmp1 = decorator2(yourfunc)
tmp2 = decorator1(tmp1)

tmp3 = decorator1(yourfunc)
tmp4 = decorator2(tmp3)

デコレーターを順番にyourfunc関数に適用していくコード

 変数tmp1にはdecorator2関数にyourfunc関数を渡した結果(関数、デコレーター)が代入され、変数tmp2には変数tmp1に代入された関数をdecorator1関数に渡した結果(関数)が渡されている。変数tmp3と変数tmp4はその順番を変えたものだ。

 簡単にいえば、変数tmp2は「decorator1(decorator2(yourfunc))」の結果が、変数tmp4には「decorator2(decorator1(yourfunc))」の結果が代入されていると考えられる。では、これらの関数を呼び出してみよう。

print('--- calling tmp2 ---')
tmp2('hello world')
print('--- calling tmp4 ---')
tmp4('hello world')

どちらがmyfunc関数と同じ結果になるかを確認するコード

 実行結果を以下に示す。

実行結果 実行結果

 このことから、複数のデコレーターで関数定義を修飾すると、「デコレーター1(デコレーター2(関数))」のように、修飾時に上に記述したデコレーターの呼び出しに、下に記述したデコレーターが渡され、さらにそのデコレーターに修飾された関数が渡されていくことが分かった。

       1|2 次のページへ

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。