目次と前回の記事
これまでに作成したモジュール
以下のリンクから、これまでに作成したモジュールを見ることができます。
リンク | 説明 |
---|---|
marubatsu.py | Marubatsu、Marubatsu_GUI クラスの定義 |
ai.py | AI に関する関数 |
test.py | テストに関する関数 |
util.py | ユーティリティ関数の定義。現在は gui_play のみ定義されている |
tree.py | ゲーム木に関する Node、Mbtree クラスの定義 |
gui.py | GUI に関する処理を行う基底クラスとなる GUI クラスの定義 |
AI の一覧とこれまでに作成したデータファイルについては、下記の記事を参照して下さい。
デコレータ式によるラッパー関数の定義
前回の記事では、下記のプログラムのような、ラップする関数を代入する仮引数 を持ち、ラッパー関数を定義して返す 処理を行う create_show_time
を定義しました。
from datetime import datetime
def create_show_time(func):
def show_time(*args, **kwargs):
starttime = datetime.now()
retval = func(*args, **kwargs)
endtime = datetime.now()
print(endtime - starttime)
return retval
return show_time
また、下記のプログラムのように、create_show_time
を利用して sum
関数に処理時間を表示する機能を追加した ラッパー関数を作成 して sum_show_time
に代入 しました。
sum_show_time = create_show_time(sum)
print(sum_show_time([10, 20]))
実行結果
0:00:00
30
デコレータとは何か
create_show_time
のような、ラップする関数を仮引数に代入し、ラッパー関数を定義して返すような高階関数の事を、Python では デコレーター(decorator) と呼びます。ラップする関数の 機能を拡張 して 飾り付けを行う(decorate) 処理を行うことから、デコレーターと名付けられたようです。
なお、デコレーターという、特別な種類の関数があるわけではありません。デコレーター はそのような処理を行う 通常の関数の一種 にすぎません。
上記では、デコレータのことを関数と説明しましたが(下記のリンク先にもそのように説明されています)、関数以外にもデコレーターの機能を持つクラスを定義する事もできます。その方法については次回の記事で説明します。
また、関数以外にも、クラスの機能を拡張した、ラッパークラスを定義するデコレーターを作成することもできます。そちらについては本記事で言及します。
デコレーターの用語の詳細については、下記のリンク先を参照して下さい。
ラッパー関数の名前
上記の例では、sum
関数のラッパー関数に sum_show_time
という名前を付けましたが、
create_show_time
によって作成されるラッパー関数は、ラップする関数と 同じ仮引数 と 同じ返り値 を持ち、元の機能を保持したまま機能を拡張 するという処理を行います。
このような場合は、ラッパー関数に ラップする関数と同じ名前を付ける ことが良くあります。具体的には、下記のプログラムの 1 行目のように、sum_show_time
によって作成された sum
のラッパー関数 の返り値を、sum
という名前の変数に代入 します。
このような処理を行うことで、2 行目のように、ラップする関数を使ったプログラムの 記述を全く変えることなく、機能を拡張したラッパー関数を使った処理が行われるようになるという利点が得られます。
sum = create_show_time(sum)
print(sum([10, 20]))
実行結果
0:00:00
30
なお、この方法には ラップする関数を利用できなくなる という欠点があります。上記の例の場合は、元の組み込み関数 sum
の処理を 利用することができなくなります。ラップする関数を 引き続き利用したい場合 は、ラッパー関数の名前をラップする関数と 異なる名前を付ける 必要があります。
デコレーター式によるラッパー関数の定義
デコレータを利用して、ラップする関数と 同じ名前のラッパー関数を定義 するという処理は、比較的よく行われます。そこで、そのようなラッパー関数を デコレーター式 という構文で 簡潔に定義 する方法が用意されています。
デコレーター式は @ で始まる構文 で、デコレーター式を利用した ラッパー関数の定義 は下記のように記述します。これまでのプログラムで クラスメソッド や 静的メソッド を定義する際に、メソッドの定義の直前の行 に記述した @classmethod
や @staticmethod
は Python で良く利用されるデコレータ式です。
@デコレータの処理を行う関数名
ラップする関数の定義
@ の後ろには、以下のような性質を持つデコレータの処理を行う関数名を記述します。前回の記事で定義した create_show_time
などが該当します。
デコレーター式のすぐ下 には、ラップする関数の定義をそのまま記述 します。例えば、下記は、仮引数 x
に代入された値に 1 を足した数を返す add_1
という関数に対して、デコレーター式 に @create_show_time
を記述して、処理時間を計算して表示する機能を追加した add_1
という名前のラッパー関数を定義 するプログラムです。
@create_show_time
def add_1(x):
return x + 1
上記を実行すると、add_1
のラッパー関数が作成 され、同じ add_1
という名前の変数に代入 されます。そのため、add_1
を呼び出す と、下記のプログラムのように 処理時間が表示される ようになります。
print(add_1(1))
実行結果
0:00:00
2
デコレーター式が行う処理
下記の 1 ~ 3 行目の デコレーター式と関数の定義 を実行した際に 行われる処理 は、下記の 5 ~ 7 行目のプログラムで行われる処理と同じ です。
1 @decorator
2 def func(*args, **kwargs): # 関数名や仮引数は何でもよい
3 pass # ここで行う処理は何でもよい
4
5 def func(*args, **kwargs): # デコレータ式の下に記述した関数の定義と同じ
6 pass
7 func = decorator(func)
そのため、下記の 1 ~ 3 行目の デコレーター式と関数の定義を実行 すると、下記 5 ~ 7 行目のプログラムと同じ処理 が行われされます。5 ~ 7 行目で行われる処理は、先程説明したデコレータである create_show_time
を呼び出して add_1
のラッパー関数を定義 して、add_1
に代入 する処理です。
1 @create_show_time
2 def add_1(x):
3 return x + 1
4
5 def add_1(x):
6 return x + 1
7 add_1 = create_show_time(add_1)
このように、デコレータ式は、別の方法で記述 できるプログラムを、より簡潔に記述するため に 導入された構文 です。このようなプログラムを 書きやすく、わかりやすく記述できるように導入 された構文のことを シンタックスシュガー(syntax sugar、糖衣構文) と呼びます。Python のシンタックスシュガーには、他にも list 内包表記などがあります。
なお、シンタックスシュガーは慣れれば便利ですが、プログラム言語に特有 のものが多く、他のプログラミング言語では利用できない場合が多い 点に注意する必要があります。そのため、例えば Python 以外のプログラミングを学んだ人にとっては list 内包表記や、デコレーター式を見てもすぐに意味が理解できない可能性が高いでしょう。また、Python で記述したプログラムを 別のプログラム言語に書き直す 必要がある場合では、シンタックスシュガーの記述の書き直しの作業が問題となる 場合があります。
デコレーター式の詳細については下記のリンク先を参照して下さい。
デコレーター式が行う処理の補足
厳密には下記のプログラムの 1 ~ 3 行目のデコレーター式が行う処理は、4 ~ 6 行目で行われる処理とは ほんの少しだけ異なります。そのことは、上記のリンク先では「だいたい次と等価です」と、「前者のコード(デコレーター式のプログラムの事です)では元々の関数を func という名前へ一時的に束縛することはない」のように説明されています。
1 @create_show_time
2 def add_1(x):
3 return x + 1
4
5 def add_1(x):
6 return x + 1
7 add_1 = create_show_time(add_1)
デコレーター式が行う処理を理解 する際に、その違いを意識する必要はありません が、その違いを知りたい人がいるかもしれないので以下に説明します。下記の説明の意味がわからない方は読み飛ばしても構いません。
「元々の関数を func という名前へ 一時的に束縛 する」という説明は、上記の 5 ~ 7 行目で行われる下記の処理の事を意味しています。
- 5、6 行目の 関数の定義を実行 することで グローバル名前空間 に、
add_1
という名前と作成された関数オブジェクトの対応づけが登録される。このような 名前とオブジェクトの対応づけの名前空間への登録 のことを オブジェクトを名前に束縛する(bound)と呼ぶ ので、この処理によって 5、6 行目に記述されている 元々の関数がadd_1
という名前に束縛 されたことになる - 7 行目の処理によって、
add_1
にラッパー関数が代入される。その結果、元々の関数のadd_1
への束縛が解除 される。このように、add_1
への元々の関数の束縛が すぐに解除される ことを、上記の説明では「一時的に束縛する」と表記している
5、6 行目で行われる元々の関数を add_1
という名前に 一時的に束縛するという処理 は、add_1
という名前のラッパー関数を定義 する際に行う必要がない 無駄な処理 です。上記の 1 ~ 3 行目の デコレーター式が行う処理 では、その 無駄な処理を行わない という点で、5 ~ 7 行目が行う処理と異なります。それが、「前者のコード(デコレーター式のプログラム)では元々の関数を func という名前へ一時的に束縛することはない」という説明の意味です。
なお、この無駄な処理が行われるかどうかによって、最終的な処理の結果は変わらない ので、上記の 違いを意識する必要はありません。
デコレーター式に関するエラー
先程説明した、下記の条件を満たさない関数 をデコレーター式に記述すると エラーが発生する ので、それらのエラーについて簡単に説明します。
- ラップする関数を代入する 仮引数を 1 つだけ持つ
- 返り値として、ラッパー関数を返す
仮引数が存在しない場合
下記のプログラムの decorator_error1
は、仮引数を一つも持たない ので、デコレーター式を実行すると実行結果のように decorator_error1
には位置引数がない(0 positional argument)にも関わらず実引数が記述されているという意味のエラーが発生します。
def decorator_error1():
pass
@decorator_error1
def add_1(x):
return x + 1
実行結果
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[5], line 4
1 def decorator_error1():
2 pass
----> 4 @decorator_error1
5 def add_1(x):
6 return x + 1
TypeError: decorator_error1() takes 0 positional arguments but 1 was given
これは、上記のプログラムは、下記のプログラムと同じ処理を行うからです。
def decorator_error1():
pass
def add_1(x):
return x + 1
add_1 = decorator_error1(add_1)
仮引数が 2 つ以上存在する場合
下記のプログラムの decorator_error2
は、仮引数を 2 つ持つ ので、デコレーター式を実行すると実行結果のように、decorator_error2
を呼び出す際に仮引数 g
に対応する位置引数が記述されていないという意味のエラーが発生します。
def decorator_error2(f, g):
pass
@decorator_error2
def add_1(x):
return x + 1
実行結果
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[6], line 4
1 def decorator_error2(f, g):
2 pass
----> 4 @decorator_error2
5 def add_1(x):
6 return x + 1
TypeError: decorator_error2() missing 1 required positional argument: 'g'
返り値が関数でない場合
下記のプログラムの decorator_error3
は、仮引数を 1 つ持つ点では問題はありませんが、返り値が関数でない2 点が問題です。ただし、下記のプログラムを実行しても その時点ではエラーは発生しません。
def decorator_error3(func):
return 1
@decorator_error3
def add_1(x):
return x + 1
エラーが発生するのは、下記のプログラムのように add_1
を呼び出した場合です。
print(add_1(1))
実行結果
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[8], line 1
----> 1 print(add_1(1))
TypeError: 'int' object is not callable
add_1
には、 decorator_error3(add_1)
の返り値が代入 されますが、decorator_error3
は 常に 1 を返す ので、add_1
には 1
が代入 されています。そのため、整数型(int)である 1
は関数のように呼び出すことはできない(is not callable)というエラーが発生します。また、実際に下記のプログラムを実行すると、add_1
に 1
が代入 されていることが確認できます。
print(add_1)
実行結果
1
デコレータ式の使用上の注意
デコレーターは便利ですが、使用する際には いくつか気を付ける必要 があるので、それらについて説明します。
ラップする関数を利用できなくなる
デコレータ式は、ラップする関数と 同じ名前のラッパー関数を定義 するので、ラップする関数を利用できなくなってしまう という欠点があります。ラップする関数を そのままの形で利用し続ける必要がある 場合は、デコレーター式を利用することはできず、下記のプログラムの 3 行目のような方法で、ラップする関数と 異なる名前の変数 に作成したラッパー関数を代入する必要があります。
def add_1(x):
return x + 1
add_1_show_time = create_show_time(add_1)
定義済の関数に対して利用できない
デコレータ式は 直後の行 に ラップする関数の定義を記述 する必要があるので、既に 定義済の関数 に対して 利用することはできません。例えば、sum
のような 組み込み関数 や、他のモジュールで定義された関数 に対してデコレーター式を利用することはできません。
docstring などの表示がラッパー関数のものになる
デコレーター式によって定義された ラッパー関数 は、名前はラップする関数と同じ になりますが、その 実態 はデコレーターの関数の中で定義された ラッパー関数 です。
そのため、ラッパー関数の docstring を表示した場合に、ラップする関数 に記述した docstring ではなく、ラッパー関数の docstring が表示 されるなどの 問題が生じます。
デコレーター式を実行する前の処理
具体例を挙げて説明します。
まず、add_1
を、下記のプログラムのように docstring を記述したプログラム として 再定義 します。
def add_1(x):
"""1 を足して返す
Args:
x: 加算する値
Returns:
x + 1 の値
"""
return x + 1
関数の定義に記述した docstring は 組み込み関数 help
を使って表示 することができ、add_1
の docstring は下記のプログラムで表示できます。
help(add_1)
実行結果
Help on function add_1 in module __main__:
add_1(x)
1 を足して返す
Args:
x: 加算する値
Returns:
x + 1 の値
また、関数が定義された時 に def 文で付けられた名前 は、関数オブジェクトの __name__
という 特殊属性に代入 されています。そのため、下記のプログラムで add_1
の __name__
属性を表示すると、実行結果のように add_1 が表示されます。
print(add_1.__name__)
実行結果
add_1
組み込み関数 help
の詳細については下記のリンク先を参照して下さい。
関数オブジェクトの特殊属性 __name__
の詳細については下記のリンク先を参照して下さい。
デコレーター式を実行した後の処理
次に、デコレーター式を実行した後 で、上記と同様の処理を行うと、どのように結果が変わるかを説明します。まず、デコレーターである create_show_time
を、下記のプログラムのように ラッパー関数 show_time
に対して docstring を記述 したプログラムとして 再定義 します。なお、デコレーターの中で定義する ラッパー関数の名前 は、wrapper
や _wrapper
という 名前にすることが多い ようですが、本記事ではこれまでと同様に show_time
のままとしました。
def create_show_time(func):
def show_time(*args, **kwargs):
"""ラップする関数の処理時間を表示するラッパー関数"""
starttime = datetime.now()
retval = func(*args, **kwargs)
endtime = datetime.now()
print(endtime - starttime)
return retval
return show_time
上記を実行後に、下記のプログラムで @create_show_time
のデコレーター式を使って add_1
に対するラッパー関数を定義します。
@create_show_time
def add_1(x):
"""1 を足して返す
Args:
x: 加算する値
Returns:
x + 1 の値
"""
return x + 1
上記のプログラムを実行すると、add_1
という名前の変数 に、create_show_time
で定義された ラッパー関数 show_time
が代入 されます。その結果、add_1
には上記のプログラムに記述した ラップする add_1
の定義とは異なる関数が代入 されることになります。
そのため、下記のプログラムで help
を使って add_1
の docstring を表示すると、実行結果のように show_time
の docstring が表示 されてしまいます。
help(add_1)
実行結果
Help on function show_time in module __main__:
show_time(*args, **kwargs)
ラップする関数の処理時間を表示するラッパー関数
同様に add_1
の __name__
属性を表示すると、実行結果のように show_time が表示されます。
print(add_1.__name__)
実行結果
show_time
functools.wrap
による問題の修正
create_show_time
のように、ラップする関数 の仮引数、返り値、処理に 変更を加えず、ちょっとした機能の追加 を行うようなラッパー関数を定義する場合は、docstring や、__name__
属性の値 は、ラッパー関数ではなく、ラップする関数の情報が表示された方が便利 です。実際に上記の help(add_1)
の実行で、show_time
の docstring が表示されても add_1
の 使い方を知ることはできません。
この問題は、ラッパー関数 に対して functools モジュールで定義された wraps
によるデコレーター式を記述 することで修正することできます。
wraps
をデコレーター式に記述する際は、下記のプログラムの 4 行目のように ラップする関数を実引数に記述して呼び出す という形で記述します。
@wraps(func)
は、その下に定義した関数の docstring や __name__
属性の値などを、実引数 func
で指定した関数の情報に置き換える という機能を持つデコレーター式です。
1 from functools import wraps
2
3 def create_show_time(func):
4 @wraps(func)
5 def show_time(*args, **kwargs):
6 """ラップする関数の処理時間を表示するラッパー関数"""
7
8 starttime = datetime.now()
9 retval = func(*args, **kwargs)
10 endtime = datetime.now()
11 print(endtime - starttime)
12 return retval
13
14 return show_time
行番号のないプログラム
from functools import wraps
def create_show_time(func):
@wraps(func)
def show_time(*args, **kwargs):
"""ラップする関数の処理時間を表示するラッパー関数"""
starttime = datetime.now()
retval = func(*args, **kwargs)
endtime = datetime.now()
print(endtime - starttime)
return retval
return show_time
修正箇所
+from functools import wrap
def create_show_time(func):
+ @wrap(func)
def show_time(*args, **kwargs):
"""ラップする関数の処理時間を表示するラッパー関数"""
starttime = datetime.now()
retval = func(*args, **kwargs)
endtime = datetime.now()
print(endtime - starttime)
return retval
return show_time
functools は、高階関数に対する様々な処理を行うためのモジュールです。functools の wraps
の詳細については、下記のリンク先を参照して下さい。
上記の修正後に、改めて下記のプログラムで add_1
に対して修正した create_show_time
を利用してラッパー関数を作成します。
@create_show_time
def add_1(x):
"""1 を足して返す
Args:
x: 加算する値
Returns:
x + 1 の値
"""
return x + 1
上記の実行後に下記のプログラムで help
を使って add_1
の docstring を表示すると、実行結果のように、再び add_1
の docstring が表示される ようになります。
help(add_1)
実行結果
Help on function add_1 in module __main__:
add_1(x)
1 を足して返す
Args:
x: 加算する値
Returns:
x + 1 の値
同様に add_1
の __name__
属性を表示すると、実行結果のように 再び add_1 が表示される ようになります。
print(add_1.__name__)
実行結果
add_1
ラッパー関数が、ラップする関数と 同じような機能を持つ場合 は、functools モジュールの wraps
をインポートし、デコレーターの中で定義されたラッパー関数に対して @wraps(func)
のデコレータ式を記述すると良い。
ラップする関数と無関係な処理を行うデコレーターをデコレーター式に記述できてしまう
デコレーター式は 一般的 には ラップする関数の機能を拡張 したラッパー関数を定義するために利用されますが、下記のプログラムの 1 ~ 3 行目のデコレーター式によるラッパー関数を作成する処理は、5 ~ 7 行目の処理を 別の方法で記述しているにすぎません。
そのため、下記の 5 ~ 7 行目 のプログラムを実行した際に エラーが発生しなければ、decorator
が どのような処理を行う関数であっても、1 ~ 3 行目のプログラムを実行することで 2、3 行目の func
のラッパー関数を定義できてしまいます。
1 @decorator
2 def func(*args, **kwargs): # 関数名や仮引数は何でもよい
3 pass # ここで行う処理は何でもよい
4
5 def func(*args, **kwargs): # デコレータ式の下に記述した関数の定義と同じ
6 pass
7 func = decorator(func)
具体的には、先程説明したように、以下の条件を満たす関数であれば、デコレーター式とその下の関数の定義を実行しても エラーは発生しません。
例えば、下記の decorator_sum
は、仮引数 func
を 1 つだけ持ち、組み込み関数 sum
を返す ので、その中で ラッパー関数を定義していません が、上記の条件を満たします。
def decorator_sum(func):
return sum
上記の定義後に、下記のプログラムのように decorator_sum
をデコレーター式に記述して、add_1
のラッパー関数を定義 する処理を実行してもエラーは発生しません。
@decorator_sum
def add_1(x):
return x + 1
上記のプログラムは、下記のプログラムと同じ処理を行います。
add_1 = decorator_sum(add_1)
decorator_sum
は、仮引数に代入された関数 とは 全く無関係 に、常に sum
という組み込み関数を返す という処理を行うので、decorator_sum(add_1)
の返り値は sum
になります。従って、上記のプログラムは、下記のプログラムと同じ処理が行われます。
add_1 = sum
上記のことから、デコレータ式に @decorator_sum
を記述 すると、その下に定義した関数にどのような処理が記述されていても、必ず sum
と同じ処理を行うラッパー関数が定義される ことになります。実際に、下記のプログラムのように、add_1([10, 20])
を実行すると、sum([10, 20])
と同じ処理が行われ、実行結果のように 30 が表示されます。
add_1([10, 20])
実行結果
30
一般的 には、上記のような ラップする関数と完全に無関係な処理を行う ような関数をデコレーター式に 記述することはありません が、上記のようなことができてしまうということは頭の片隅にでも入れておいたほうが良いでしょう。
実引数と返り値がそのまま使われない場合がある
ラッパー関数の中で、ラッパー関数の 仮引数に代入された値を全く利用せず に ラップする関数を呼び出す ようなデコレーターを定義する事ができます。
例えば、下記のプログラムの decorator_arg10
は、下記のような処理を行うラッパー関数を返します。
- ラップする関数
func
の 実引数に必ず10
を記述して呼び出し、その返り値を返す処理を行う - その際に、ラッパー関数
wrapper
の 仮引数args
とkwargs
の値を一切利用しない
def decorator_arg10(func):
def wrapper(*args, **kwargs):
return func(10)
return wrapper
その結果、下記のプログラムのように、decorator_10
をデコレーター式に記述して add_1
の ラッパー関数を定義 すると、ラッパー関数の 実引数 にどのような値を記述して呼び出してもその 値は無視され、ラップする元の関数の add_1
の 実引数に常に 10 が記述 して呼び出された場合の処理が行われます。
@decorator_arg10
def add_1(x):
return x + 1
実際に、下記のプログラムを実行すると、実行結果では add_1
の 実引数の値に関わらず常に 11 が表示 されます。
print(add_1(1))
print(add_1(100))
実行結果
11
11
同様に、下記のプログラムのようなデコレータを定義してデコレーター式に記述すると、実引数にどのような値を記述しても 必ず 10 が返る ようなラッパー関数が作成されます。
def decorator_ret10(func):
def wrapper(*args, **kwargs):
return 10
return wrapper
@decorator_ret10
def add_1(x):
return x + 1
print(add_1(1))
print(add_1(100))
実行結果
10
10
上記のような、実引数を完全に無視 したり、返り値を特定の値にしてしまう ようなデコレータを 定義して利用することはほぼない と思いますが、こちらもこのようなことができてしまうことは頭の片隅にでも入れておいたほうが良いでしょう。
なお、以前の記事で紹介した関数呼び出しの記述の簡略化や、データー構造の変更のように、ラップする関数の 仮引数の一部に特定の値を代入 にしたり、返り値に対して何らかの計算を行って返す ようなラッパー関数を定義する場合は 実際にあります。先ほど functools.wraps
を紹介しましたが、そのような場合は、ラッパー関数の定義の前に functools.wraps
を記述しても、ラップする関数の docstring の内容 が、ラッパー関数で行う処理と大きく異なってしまう ことになるので、あまり有用ではないかもしれません。
なお、この点に関する筆者のデコレーターに対する知識がまだ曖昧なので、詳しい方はコメントで補足して頂ければ助かります。
ここまでの内容をまとめると以下のようになります。
デコレーター式 によるラッパー関数の定義の多くは以下のようなものが多い。そのような場合は、デコレーターの中でラッパー関数を定義 する際に、@wraps(func)
のデコレータ式を記述すると良い。
- ラップする関数の仮引数をそのまま利用する
- ラップする関数の返り値をそのまま返す
- 行う機能の拡張はラップする関数の処理を大きく変えるものではない
ただし、上記の条件を満たさないようなデコレーター式を記述してラッパー関数を定義する場合もある点に注意が必要である。
複数のデコレーター式の記述
一つのラップする関数の定義 に対して、下記のプログラムのように、複数のデコレーター式を記述 することができます。このような場合は、下に記述されたデコレーター式から順番 に処理が行われます。
@decoratorA
@decoratorB
def func():
pass
具体的には、上記のプログラムでは、下記の処理が行われます。
func = decoratorA(decoratorB(func))
詳細を説明すると長くなるので今回の記事ではでは具体的な説明を省略します。
デコレーター式の種類
デコレーター式にはいくつかの種類がある のでそれらについて簡単に説明します。
関数呼び出しを記述するデコレーター式
先程紹介した functools モジュールの wraps
をデコレーター式に記述する際に、@wraps(func)
のように、関数呼び出しをデコレーター式に記述 しました。
このような記述を行うことができるようにするためには、デコレーターの関数の定義 を今回の記事で説明した方法とは 若干異なる方法で行う 必要があります。ただし、その方法を説明すると記事がかなり長くなってしまうので、今回の記事では説明しません。また、wraps
のようなデコレーターを 利用するだけ であれば、どのように定義するかを知る必要はありません。そのようなデコレータの定義の方法については、必要が生じた場合に説明したいと思います。
なお、そのようにデコレータ式を記述した場合でも、行われる処理が特に変わるわけではありません。例えば、下記の 1 ~ 3 行目のプログラムは、5 ~ 7 行目のプログラムと同じ処理を行います。
1 @decorator(arg)
2 def func():
3 pass
4
5 def func():
6 pass
7 func = decorator(arg)(func)
先程との違い
-@decorator
+@decorator(arg)
def func():
pass
def func():
pass
-func = decorator(func)
+func = decorator(arg)(func)
先程は 1 行目が @decorator
、7 行目が decorator(func)
でしたが、上記では 1 行目が @decorator(arg)
、7 行目が decorator(arg)(func)
のように変わっており、具体的には decorator
を decorator(arg)
に 置き換えているだけ です。従って、デコレータ式が行う 処理の性質は全く同じ です。
メソッドに対するデコレーター式
これまでは関数の定義に対してデコレーター式を記述しましたが、メソッドに対しても同様の方法 でデコレーター式を記述できます。
下記は A
というクラスの add_1
というメソッドに対して @create_show_time
のデコレータ式を記述 したプログラムです。
-
2、3 行目:インスタンスの作成時に、属性
x
に 1 を代入する -
6 ~ 8 行目:属性
x
の値を 1 加算し、その値を表示する
1 class A:
2 def __init__(self):
3 self.x = 1
4
5 @create_show_time
6 def add_1(self):
7 self.x += 1
8 print(self.x)
行番号のないプログラム
class A:
def __init__(self):
self.x = 1
@create_show_time
def add_1(self):
self.x += 1
print(self.x)
上記のプログラムを実行後に、下記のプログラムを実行して add_1
を呼び出すと、実行結果に処理時間が表示されることから、add_1
が 処理時間を表示 する機能が追加された ラッパーメソッドになっている ことが確認できます。
a = A()
a.add_1()
実行結果
2
0:00:00
クラスに対するデコレーター式
クラスに対してもデコレーター式を利用して、クラスの機能を拡張したラッパークラスを定義する事ができます。ただし、下記の公式ドキュメントに「同じ概念がクラスにも存在しますが、あまり使われません」と記述されているように、クラスに対するデコレーター式はあまり使われないようなので、本記事では説明を省略します。
クラスに対するデコレーター式の処理については、下記のリンク先を参照して下さい。
クラスによるデコレータの定義
これまでの記事では、デコレーターを関数として定義しましたが、デコレーターをクラスとして定義する事もできます。その説明は次回の記事で行います。
今回の記事のまとめ
今回の記事では、デコレーターとデコレーター式を利用したラッパー関数の定義について説明しました。
本記事で入力したプログラム
リンク | 説明 |
---|---|
marubatsu.ipynb | 本記事で入力して実行した JupyterLab のファイル |
次回の記事