Python3.8.5におけるデコレーターについて。
def a():
def b():
return 'bです。'
return b
print(a) #a関数オブジェクトの出力
print(a()) #b関数オブジェクトの出力
print(a()()) #b関数の実行
#print(b()) #インナー関数の直接的な実行はできない
#②引数のある関数の実行
def a(name):
return 'hello!' + name
b = a('jobs')
print(b)
#③関数の引数に別の関数を指定して実行
def a(func):
return func
def b(name):
return 'hello!' + name
c = a(b)
print(a(b)('jobs')) #どっちでも出力結果は同じ
print(c('jobs')) #同上
#④関数Cの処理を関数aおよびインナー関数bで装飾して実行
def a(func):
def b():
print('スタート')
func()
print('おわり')
return b
def c():
print('C関数を実行しています。')
a(c)()
print(a(c)()) #関数内ですでにprintしてるからここでのprintはいらない
#⑤④をデコレーターで処理したパターン
def a(func):
def b():
print('スタート')
func()
print('おわり')
return b
@a
def c():
print('C関数を実行しています。')
c() #a関数のデコレーターがあるのでa(c)()と同じ意味になる
print(c()) #関数内ですでにprintしてるからここでのprintはいらない
#⑥④のような関数の装飾処理をより複雑化したパターン
def aaa(func): #②関数dddを実行したはずなのになぜか最初に実行される
def bbb(*args, **kwargs): #③関数dddへの引数を受け取るための変数が必要
print('スタート') #④スタートという文字列を出力
ccc = func(*args, **kwargs) #⑤ここで関数dddを実行
print('おわり') #⑥おわりという文字列を出力
return ccc #⑨関数dddの処理結果を返す
return bbb #⑩関数dddの返り値をさらに返す
def ddd(x, z): #⑦なぜか最初に実行されないし位置引数もここには入らない
return x + z #⑧10+20=30
f = aaa(ddd)(10, 20) #①funcに関数dddを入れつつdddの位置引数を指定してdddを実行
print(f) #⑪関数dddの処理結果の出力
#⑦⑥をデコレーターで処理したパターン
def aaa(func):
def bbb(*args, **kwargs):
print('スタート')
ccc = func(*args, **kwargs)
print('おわり')
return ccc
return bbb
@aaa
def ddd(x, z):
return x + z
f = ddd(25, 25) #デコレーターを使っているのでこの記述でaaa(ddd)(25, 25)と同じ処理になる
print(f) #関数dddの処理結果の出力
デコレーターとは、関数の定義時にその関数の機能を装飾(デコレーション)することを意味します。
別の言い方をするなら、対象の関数aに対して別の処理をする関数bの機能を付け加える、というような感じです。
「インナー関数(関数内関数)」と「クロージャ―(関数引数)」について理解していないと、デコレーターを理解するのはまず無理だと思います。
なので、そちらの基礎の部分もあわせて見ていきたいと思います。
上記のプログラムの出力結果は、それぞれ以下のようになります。
①の出力結果
<function a at 0x0000021B57B17160>
<function a.<locals>.b at 0x0000021B5813DF70>
bです。
②の出力結果
hello!jods
③の出力結果
hello!jods
hello!jods
④の出力結果
スタート
C関数を実行しています。
おわり
スタート
C関数を実行しています。
おわり
None
⑤の出力結果
スタート
C関数を実行しています。
おわり
スタート
C関数を実行しています。
おわり
None
⑥の出力結果
スタート
おわり
30
⑦の出力結果
スタート
おわり
50
①のプログラムについての説明。
def a():
def b():
return 'bです。'
return b
print(a) #a関数オブジェクトの出力
print(a()) #b関数オブジェクトの出力
print(a()()) #b関数の実行
#print(b()) #インナー関数の直接的な実行はできない
これは引数のないインナー関数のパターンです。
見てわかるように、関数aの中には関数bオブジェクトそのものが入っています。
そして関数bを実行すると、「bです。」という文字列が返ってきます。
なのでこのようなパターンでは、a() と記述して関数aを実行してやるだけでは、関数aの中に格納されている関数bオブジェクトそのものが出力されるだけであり、「bです。」という文字列は出力されません。
関数bを実行するには、a()() と記述する必要があります。
②のプログラムについての説明。
def a(name):
return 'hello!' + name
b = a('jobs')
print(b)
これは関数aに指定されたnameという変数内に引数を入れてやるパターンです。
関数aを実行する際に () の中に値を記述することで、その値を引数として関数aが処理をおこなってくれます。
上記のプログラムでは「jods」と記述しているので、関数aの返り値は「hello!jods」となります。
③のプログラムについての説明。
def a(func):
return func
def b(name):
return 'hello!' + name
c = a(b)
print(a(b)('jobs')) #どっちでも出力結果は同じ
print(c('jobs')) #同上
これは関数aの引数funcに関数bを入れたパターンです。
a(b) の部分でfuncの引数を指定しています。
ただこのパターンでは関数bにも変数nameへの引数が必要です。
なのでこのプログラムを実行するには、a(b)(nameの値) という形で関数aに格納された関数bを実行しつつ、関数bのnameへの引数も指定しないといけません。
上記のプログラムの出力結果は「hello!jobs」となります。
④のプログラムについての説明。
def a(func):
def b():
print('スタート')
func()
print('おわり')
return b
def c():
print('C関数を実行しています。')
a(c)()
print(a(c)()) #関数内ですでにprintしてるからここでのprintはいらない
これはデコレーターを使うべきプログラムを、デコレーターを使わずに処理しているパターンです。
このプログラムを解読できればデコレーターの概要は理解できます。
がんばりましょう。私はがんばりました。
このプログラムでは、関数cの処理をインナー関数bの中でおこなっています。
func()の部分ですね。
ここで関数aの中に格納されている関数cオブジェクトを実行しています。
a(c)() と記述して変数funcに関数cを入れつつ、すでに関数a内にあった関数bオブジェクトを実行します。
その結果関数bが実行されて、まず最初に「スタート」が出力されます。
次に func() で変数func内に格納されている関数cオブジェクトが実行されるので、「C関数を実行しています。」が出力されます。
最後に「おわり」が出力されて、このプログラムは終了します。
「なぜfuncに関数cを入れたのに値が上書きされずに最初から入っていた関数bが実行されるのか?」とかは聞かないでください。
そんなことは私が聞きたいくらいです。
さてものすごくややこしいですが、要はこのプログラムは、関数cの処理の前後を別の関数で装飾してやった状態であると言えます。
このような場合、次のパターン⑤のようなデコレーターが使えます。
⑤のプログラムについての説明。
def a(func):
def b():
print('スタート')
func()
print('おわり')
return b
@a
def c():
print('C関数を実行しています。')
c() #a関数のデコレーターがあるのでa(c)()と同じ意味になる
print(c()) #関数内ですでにprintしてるからここでのprintはいらない
このプログラムは、④のプログラムをデコレーターで処理したパターンです。
関数cの上の行に @a と記述することで、関数cを関数aでデコレーションすることができます。
こうすることで、a(c)() とややこしい記述をせずにシンプルに c() と書いて関数cを実行してやるだけで、④のプログラムのように関数bを実行することができます。
このような @関数名 で関数を装飾してやる処理のことをデコレーターと言います。
⑥のプログラムについての説明。
def aaa(func): #②関数aaa内の関数bbbオブジェクトが実行される
def bbb(*args, **kwargs): #③位置引数が格納される
print('スタート') #④スタートという文字列を出力
ccc = func(*args, **kwargs) #⑤ここで関数dddを実行
print('おわり') #⑥おわりという文字列を出力
return ccc #⑨関数dddの処理結果を返す
return bbb
def ddd(x, z): #⑦*argsの値と**kwargsの値がXとyに位置引数として入る
return x + z #⑧10+20=30
f = aaa(ddd)(10, 20) #①funcに関数dddを入れつつ関数dddの位置引数を指定して関数dddを実行
print(f) #⑩関数dddの処理結果の出力
このプログラムは、④のようなパターンのプログラムをより複雑化したものです。
上記のプログラムですが、処理の流れはコメントのとおりです。
まず aaa(bbb)(10, 20) として関数dddの位置引数を指定しつつ関数aaa内の関数bbbオブジェクトを実行します。
すると、*argsと**kwargsの中に位置引数として指定した10と20がそれぞれ格納されます。
そのあとで「スタート」という文字列が出力されます。
そして func(*args, **kwargs) の部分で、変数func内に格納されている関数dddオブジェクトを実行します。
このときの*argsと**kwargs内の値はそれぞれ位置引数としてxとzの中に格納されます。
そしてその x+z の処理結果が、返り値として変数cccの中に格納されます。
あとは、その変数cccの返り値を受け取った変数fの中身(50)をprint関数で出力して、このプログラムは終了します。
⑦のプログラムについての説明。
def aaa(func):
def bbb(*args, **kwargs):
print('スタート')
ccc = func(*args, **kwargs)
print('おわり')
return ccc
return bbb
@aaa
def ddd(x, z):
return x + z
f = ddd(25, 25) #デコレーターを使っているのでこの記述でaaa(ddd)(25, 25)と同じ処理になる
print(f) #関数dddの処理結果の出力
⑥のプログラムの処理が理解できたのであれば、デコレーターという処理を理解することは可能だと思います。
関数dddの上の行に@aaaと記述することで、関数dddを関数aaaの処理で装飾します。
関数aaaに格納されている関数bbbオブジェクトを実行するには、ddd(第一引数 , 第二引数) と記述してやればOKです。
以上です。
●参考サイト
今回はUdemyで買ったの定価24000円のPython講座だけでは理解できなかったので、<【Python】初心者向けにデコレータの解説> という記事をがっつりと参考にさせていただきました。
すごくわかりやすくて、自分でもなんとかデコレーターの概要を理解することができました。
作者さん、本当にありがとうございました。