はじめに
僕は全然デコレータの動きを覚えることができませんでした。
しかし、ようやく他の人に説明できると思えるようになったので、簡単な例を使って段階的に動きを見ていきます。
この記事では以下の主な3種類のデコレータの動きのうち①について書きます。
(1つの記事で全部紹介しようと思いましたが、体力が尽きました。)
①デコレータ使用時に引数を受け取らないパターン
def deco1(func):
# 処理
return decooo1
@deco1
def foo():
# 処理
return foooo
foo()
②デコレータ使用時に引数を受け取るパターン
def deco2(boo):
# 処理
return decooo2
@deco2(val, arg1=val2)
def foo2():
# 処理
return foooo2
foo2()
③デコレータがクラスで実装されているパターン
class deco3:
# 処理
@deco3(arg=val)
def foo3():
# 処理
return foooo3
foo3()
デコレータの基本的な動き
「はじめに」で書いたようにいくつかの書き方がありますが、基本的な動きは同じです。
- @の直後に置かれたcallableオブジェクト1に次の行で定義している関数を引数として渡して呼び出す
- @の次の行で定義している関数名に1で呼び出した返り値を代入する
文字だけだと訳がわからないので、段階的に説明を進めていきます。
なお、以降deco
は関数で定義されたデコレータとします。
###ステップ1:@deco
の次の行に関数を定義すると、その時点でdeco
関数が実行される
def deco(func):
print('deco excecuted')
@deco
def test():
print('test')
paiza.ioで実行してみる(↑のコードをコピーして遷移先で貼り付けて実行してください)
以下のように出力されました。
deco excecuted
つまり、deco
関数が実行されました。
ここまでで以下のことがわかりました。
@deco
の次の行に関数を定義すると
- その時点で
deco
関数が実行される
簡単ですね。
ステップ2:@deco
の次の行に定義した関数名にはdeco
関数の返り値が代入される
def deco(func):
return 1
@deco
def test():
print('test')
print(test)
paiza.ioで実行してみる(↑のコードをコピーして遷移先で貼り付けて実行してください)
1
が出力されました。つまりデコレータのせいで変数testは関数ではなく、整数1
を保持するようになりました。
ここまでで以下のことがわかりました。
@deco
の次の行に関数を定義すると
- その時点で
deco
関数が実行される -
@deco
の次の行で定義された関数名にはdeco
関数の返り値が格納される
###ステップ3:deco
関数の引数には@deco
直後で定義された関数が渡される
def deco(func):
func()
return 1
@deco
def test():
print('test')
paiza.ioで実行してみる(↑のコードをコピーして遷移先で貼り付けて実行してください)
以下のように出力されました。
test
test()
とは書いていないのに、testが出力されました。これはdeco
関数の引数func
にtest
関数が代入され、func()
によりtest
関数が実行されているためです。
ここまでで以下のことがわかりました。
@deco
の次の行に関数を定義すると
-
deco
関数が実行される -
deco
関数の返り値は@deco
の次の行で定義された関数名に代入される -
deco
関数の引数には@deco
の次の行で定義された関数が入っている
ステップ4:ステップ1〜3を組み合わせてデコレータを活用してみる
ここまで理解できればもう何も怖くありません。①のパターンのデコレータに関する必要な知識はこれだけです。
もしここでつまずく場合は、*args
と**kwargs
などを復習してみてください。
ここでは名前を引数として受け取るとその人を紹介するintroduce
関数を定義します。
そして、もしその人が兄弟であればその人を紹介する前に「This is my brother.」を出力するデコレータbrother_deco
を定義します。
brother_list = ("John", "Bob")
def deco(func):
def replacing_func(*args, **kwargs):
if kwargs.get("name") in brother_list:
print(f'{kwargs.get("name")} is my brother.')
func(*args, **kwargs)
return replacing_func
@deco
def introduce(name: str):
print(f"This is {name}.\n")
introduce(name="Bob")
introduce(name="Taro")
paiza.ioで実行してみる
以下のように出力されました。
Bob is my brother.
This is Bob.
This is Taro.
つまり、@deco
の次の行にintroduce
が定義されているので、ステップ1より、この時点でdeco
関数が実行されます。
ステップ2よりdeco
関数は引数としてintroduce
を受け取るので、deco
関数内のfunc
をintroduce
に置き換えて考えることができます。
deco
関数はreplacing_func
関数を返すので、ステップ3よりintroduce
という関数名にはreplacing_func
が代入されます。
そして最後から2行目でintroduce(name=="Bob")
が実行されています。
replacing_func
関数のkwargs={'name': 'Bob'}より、kwargs.get("name") in brother_list
はTrueです。
したがって、Bob is my brother
が出力されます。
最後にfunc(*args, **kwargs)
により、This is Bob
が出力されました。
最後に
もし理解できなかった、間違っているなどありましたらご指摘していただけると助かります。
ステップ1~3までは簡単かと思いますが、ステップ4になると*args, **kwargsも相まってややこしくなりますが、一つずつ処理を追っていけば理解できると思います。
今度は②、③のパターンの記事も作ろうと思います。