LoginSignup
51
53

[Python] 絶対わかるデコレータの説明

Last updated at Posted at 2023-01-18

説明

デコレータは他の関数の機能を変更したり拡張したりすることができる関数です。

使用方法

まずデコレータ関数を定義し、
@decorator_nameという構文を修正したい関数に追加します。

def my_decorator(func):
    def wrapper():
        print("start")
        func()
        print("end")
    return wrapper

@my_decorator
def say_hello():
    print("hello")

say_hello()

実行結果:

start
hello
end

說明

@my_decoratorはPythonの糖衣構文です。
say_hello関数をmy_decorator関数の引数として渡すことができます。

簡単に言うと、@my_decoratorは以下の記述と同じです。

say_hello = my_decorator(say_hello)

つまり、元の関数をデコレータ関数の中に入れて実行することで、元の関数がデコレータの戻り関数に置き換わるのです。
say_hello()を呼び出す時、実際には元のsay_hello()ではなく、wrapper()関数を呼び出しているのです。

引数のある例

def count_and_sum(func):
    def wrapper(*args, **kwargs):
        wrapper.count += 1
        result = func(*args, **kwargs)
        wrapper.sum += result
        return result
    wrapper.count = 0
    wrapper.sum = 0
    return wrapper

@count_and_sum
def add(x, y):
    return x + y

print(add(1, 2)) # 3
print(add(3, 4)) # 7
print(add(5, 6)) # 11
 
print("Total count: ", add.count) # 3
print("Total sum: ", add.sum) # 21

@count_and_sumをaddの前に付けると、addがcount_and_sumの引数として渡されます。
count_and_sumでは、wrapperがaddを引数に取り、addが呼ばれた回数と合計を計算します。
add(x, y)が呼ばれるたびに、実際にはwrapper(x, y)が呼ばれ、addが呼ばれた回数と合計が加算されることになります。
count_and_sumはwrapperを返すので、元のaddを統計機能のある関数に変更することができます。

add.countとadd.sumはwrapperで定義された変数です。
addを@count_and_sumで変更すると、wrapperが実行されるので、wrapper内の変数が生成されてaddに格納され、add.countとadd.sumが値を持つことができるようになります。

QA

1. 糖衣構文とは?

糖衣構文(syntactic sugar)とはプログラミング言語で提供される構文で、
コードをよりシンプルに見せ、理解しやすくするが、プログラムの機能には影響を与えないものです。

2. なぜwrapperとfuncは*args, **kwargsを追加するんですか?

修正された関数(func)がいくつの引数を持ち、それらが位置引数なのかキーワード引数なのかが不明なためです。
*args、**kwargsは任意の数の位置引数とキーワード引数を取ることができるので、デコレータが元の関数の引数の数や種類に影響されないことを保証します。

3. 位置引数とキーワード引数とは?

  • 位置引数(positional arguments)
    関数が呼び出された時に、関数内で定義された引数の順番で提供される引数です。
def test_function(a, b, c):
    print(a, b, c)

test_function(1, 2, 3)

1はaの位置引数、2はbの位置引数、3はcの位置引数です。

  • キーワード引数(keyword arguments)
    関数が呼び出された時に、引数名を明示的に指定して提供される引数です。
def test_function(a, b, c):
    print(a, b, c)

test_function(a=1, c=3, b=2)

a=1はaのキーワード引数で,c=3はcのキーワード引数で,b=2はbのキーワード引数です。

キーワード引数は任意の順序で入力できるが、
位置引数は関数で定義された順序で入力する必要があります。

4. *argsとは?

*argsの機能は、任意の数の位置引数を受け取ることです。

def test_function(*args):
    for arg in args:
        print(arg)
test_function(1,2,3,4,5)

実行結果:

1
2
3
4
5

関数を呼び出す際に任意の数の引数を渡すことができ、
*argsを使って引数を受け取ることができるのです。

5. **kwargsとは?

**kwargsの機能は、任意の数のキーワード引数を受け取ることです。

def test_function(**kwargs):
    for key, value in kwargs.items():
        print(key, value)
test_function(a=1, b=2, c=3)

実行結果:

a 1
b 2
c 3

kwargs.items()を介してすべてのキーワード引数を取得し、辞書のような構造に格納します。
さらにキーから値を取得することができます。

6. argsと**kwargsを併用しない場合はどうなりますか?

1つだけだと制限があります。
例えば、*argsのみを使用した場合、位置引数は受け取れるが、キーワード引数は受け取ることができません。
**kwargs のみを使用した場合、位置引数を受け取ることができません。
したがって、デコレータは通常、任意の数の位置引数とキーワード引数を同時に取るために、両方とも使います。

51
53
2

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
51
53