LoginSignup
1

More than 1 year has passed since last update.

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

Last updated at Posted at 2020-12-12

はじめに

僕は全然デコレータの動きを覚えることができませんでした。
しかし、ようやく他の人に説明できると思えるようになったので、簡単な例を使って段階的に動きを見ていきます。

この記事では以下の主な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()

デコレータの基本的な動き

「はじめに」で書いたようにいくつかの書き方がありますが、基本的な動きは同じです。

  1. @の直後に置かれたcallableオブジェクト1に次の行で定義している関数を引数として渡して呼び出す
  2. @の次の行で定義している関数名に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関数の引数functest関数が代入され、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関数内のfuncintroduceに置き換えて考えることができます。
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も相まってややこしくなりますが、一つずつ処理を追っていけば理解できると思います。

今度は②、③のパターンの記事も作ろうと思います。

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
What you can do with signing up
1