2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

TORICOAdvent Calendar 2020

Day 19

【python】今度こそ理解できるデコレータの動き②引数を受け取るデコレータ

Last updated at Posted at 2020-12-19

はじめに

この記事は【python】今度こそ理解できるデコレータの動き①引数を受け取らないデコレータ
の続きです。

この記事のゴールは以下の形のデコレータを理解することです。

def deco(a):
    # 処理

@deco(a="hello")
def test():
    # 処理

最小限のコードで引数を受け取るデコレータの動きを確認していきます。

最初に@の後ろの関数が実行される

def foo(b):
    pass

def deco(a):
    print(a)
    return foo

@deco(a="hello")
def test():
    pass

# 実行結果
# hello

1行ずつ見ていきます。
1~6行目は関数を定義しているだけなので何も起きません。

8行目の@deco(a="hello")に到達すると、はじめに deco(a="hello")が実行されます。
4~6行目関数decoが実行され、aに"hello"が渡されると文字列helloが出力されます。
そして関数fooを返します。

頭の中は↓のような感じです。
@deco(a="hello")deco(a="hello")が実行されて関数fooが返るから、
deco(a="hello")fooと置き換わり、8行目は@fooになった。

ここまでをまとめると、

  • コードが@deco(引数)に到達すると、deco(引数)がまず実行される

※これ以降は【python】今度こそ理解できるデコレータの動き①引数を受け取らないデコレータ
ステップ1~3と全く同じです。
なので以降ざっと動きをみます。細かい動きは↑を読んでみてください。

以降は引数を受け取らないデコレータと全く同じ

def foo(func):
    print(func.__name__)
    return func

def deco(a):
    print(a)
    return foo

@deco(a="hello")
def test():
    print("inside test func")
    pass

test()

# 出力
# hello
# test

8行目はステップ1と同様にdeco(a="hello")が実行され、helloが出力された後に関数fooが返されます。
この時点で@deco(a="hello")を頭の中で@fooに置き換えます。

foofuncの__name__属性を出力して、funcを返す関数です。
@deco(a="hello")を頭の中で@fooに置き換えると、@の後ろには関数fooがあります。
@の後ろの関数(この場合foo)の第一引数には@の次の行の関数が渡されます。

よって、イメージ的には、

  1. @deco(a="hello")の時点でdeco(a="hello")が実行される。
  2. deco(a="hello")が実行されることで helloが出力され、関数fooが返る
  3. これ以降@deco(a="hello")@fooとして考えられる
  4. @fooの直後の関数testが定義される
  5. 関数testが関数fooに引数として渡されてfooが実行される
  6. 変数testfooの返り値(=関数test)が代入される
  7. test()を実行するとinside test funcが出力される

のような感じです。

ポイントは、関数decoは関数(callable)を返さないといけないということです。
なぜなら関数を返さないと呼び出すことができないのでエラーが起きるからです。
例)deco関数のreturn文を return 123とかにすると、上のイメージの5番で123(test)のようにint型の123を呼び出そうとしてエラーになります。

実験・エラー集(色々いじると理解が進みます)

decoが関数(callable)を返さない場合

def deco(a):
    print(a)
    return 123

@deco(a="hello")
def test():
    print("inside test func")
    pass

# 結果
# hello
# Traceback (most recent call last):
#   File "test.py", line 5, in <module>
#     @deco(a="hello")
# TypeError: 'int' object is not callable

イメージ的には以下のような感じです。

  1. deco(a="hello")が実行され、123が返される
  2. @deco(a="hello")を頭の中で@123に書き換える
  3. @123の直後にdef test():があるので、関数testが定義される
  4. 123(test)が実行され、'int' object is not callableが出力される。これは123は関数でないので、呼び出すことができないからです。

decoが返す関数の返り値が関数(callable)でない場合

def foo(func):
    print(func.__name__)
    return "aaa"

def deco(a):
    print(a)
    return foo

@deco(a="hello")
def test():
    print("inside test func")
    pass

test()

# 結果
# hello
# test
# Traceback (most recent call last):
#   File "test.py", line 17, in <module>
#     test()
# TypeError: 'str' object is not callable

イメージは以下のような感じです。

  1. deco(a="hello")が実行され、関数fooが返される
  2. @deco(a="hello")を頭の中で@fooに置き換える
  3. @fooの後ろで関数testが定義される
  4. 関数fooに関数testが渡される
  5. 関数fooが文字列"aaa"を返す
  6. 変数testに文字列"aaa"が格納される
  7. test()"aaa"()を行っているので、'str' object is not callableが出力される。"aaa"は関数ではないため、このエラーが起きてます。

さいごに

よくわからない場合は色々実験してみると動きが理解しやすくなるのでおすすめです。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?