ホーム >  Python >  Pythonのデコレーターで関数を修飾してみよう

投稿日:   |  最終更新日:

Pythonのデコレーターで関数を修飾してみよう

Python

Pythonのデコレーター機能について検証してみます。

Pythonで共通の関数を持たせたい

Pythonを使う上で、複数の関数に共通の処理を持たせます。

例えば、ログ出力です。ログ出力は、横断的な要素のためサブクラスを作って毎回実装するのは適切ではありません。しかし、ログ出力が必要な関数内でその都度ログ出力関数を呼び出すのも非常に面倒です。

そんなときのために、Pythonには「デコレーター」という機能が用意されています。関数とクラス、2通りの書き方があります。以下は、デコレーター「@logger」とデコレート対象の関数「def add(x, y)」です。

>>> @logger
... def add(x, y):
...     print(x+y)
... 

デコレーターとは?

デコレーターとは、関数のラッパー(包むもの)です。

デコレート(修飾)する対象の関数に新しい処理を付け加えます。ひとつデコレーターを作れば、後は複数の関数に簡単にデコレーターを指定できます。ログのような機能横断的な共通処理をつくることができます。

環境

Python 3.7.3

関数でのデコレーターの使い方

関数としてデコレーターを以下のように定義します。

>>> def logger(func):
...     def wrapper(*args):
...        print("--- call {0} ---".format(func.__name__))
...        func(*args)
...        print("--- call {0} ---".format(func.__name__))
...     return wrapper
... 
>>> @logger
... def add(x, y):
...     print(x+y)
... 

解説

>>> def logger(func):

デコレーターとしてlogger関数を定義しています。引数の「func」は、デコレート対象の関数が入ります。

このように関数を受け取る関数を高階関数と呼びます。

...        print("--- call {0} ---".format(func.__name__))
...        func(*args)
...        print("--- call {0} ---".format(func.__name__))

関数内の「func(*args)」は、ネスト(入れ子に)した関数を定義しています。

このように関数を受け取る関数を高階関数と呼びます。

ネストした関数(wrappaer)は、親の関数(logger)のローカルスコープ(func)にアクセスできます。

「__name__」属性からは、関数名を取得できます。 wrapper関数は、可変長引数(引数をいくつでも受け取れる)になっており、それをそのままデコレート対象の関数の引数として渡しています。(func.__name__)

そして最後に、「return wrapper」でwrapper関数を返しています。

解説

>>> @logger
... def add(x, y):
...     print(x+y)
... 
>>> add(2, 3)

①関数をデコレートするには、関数定義の前に”@(デコレーター名)”を指定します。後は通常通り関数を呼び出すだけです。

②すると、先程のデコレーターが呼び出され、関数呼び出しの前後に処理が追加されます。

出力結果:

--- call add ---
5
--- call add ---


デコレーターのからくり

これと

>>> add(2, 3)

これは、等価です。

>>> decorate = logger(add) 
>>> decorate(2, 3)

解説

>>> decorate = logger(add) 

logger関数にデコレートする関数を渡すと、それをラップしたwrapper関数が返ってきます。

>>> decorate(2, 3)

後はそれに通常通りの引数を渡します。するとwrapper関数の内部で、追加の処理とデコレートする関数(add)が呼び出されます。


クラスでのデコレーターの使い方

デコレーターは、クラスとしても定義できます。クラスの詳細については以下の記事を参考にして下さい。

Pythonのクラスとインスタンス

関数の実行時間を計測して出力します。

>>> from time import *
>>> 
>>> class timer:
...     def __init__(self, func):
...         self.func = func
...     def __call__(self, *args):
...         start = time()
...         self.func(*args)
...         difftime = time() - start
...         print("execute time: {0}".format(difftime))
... 
>>> @timer
... def slooop():
...     i = 0
...     while i < 10000000:
...         i += 1
... 
>>> slowloop()

出力結果:

execute time: 0.6520891189575195

解説

...     def __init__(self, func):
...         self.func = func

クラスでデコレーターを定義する場合には、「__init__」でデコレートする関数を受け取って自身の変数に保存しておきます。

...     def __call__(self, *args):
...         start = time()
...         self.func(*args)
...         difftime = time() - start
...         print("execute time: {0}".format(difftime))

デコレーターが呼び出されると「__call__」が実行されます。

先ほどのwrapper関数と同じように追加の処理とデコレートする関数( self.func(*args) )を呼び出します。

>>> @timer
... def slooop():
(省略)

デコレートする関数は、同じように”@(デコレーター名)”を指定するだけです。


次回

デコレーターの条件について整理したいと思います。

Pythonのデコレーターで関数を修飾してみよう その2 デコレータの条件


トラックバック用のURL
プロフィール

名前:イワサキ ユウタ 職業:システムエンジニア、ウェブマスター、フロントエンドエンジニア 誕生:1986年生まれ 出身:静岡県 特技:ウッドベース 略歴 20

最近の投稿
人気記事
カテゴリー
広告