3
13

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 5 years have passed since last update.

pythonの関数をデコレータ(@関数)で使用する

Last updated at Posted at 2019-10-09

はじめに

flaskなどで@app.route()を使用していましたが、@とは何だろうと思って以前調べましたが複雑で理解を断念していました。
最近、ふと気になって調べてみると理解ができて、色々できそうでしたのでまとめました。
厳密に説明を書くとわかりにくくなってしまうので、分かりやすさ優先で書いています。
参考にするときは、説明は雰囲気を感じて具体例で正確な理解をしてください。

2019/10/10
[色々な使い方]に[関数実行時にリクエストを発行してレスポンスを引数に入れる]を追加しました。
[デコレータ関数に引数を渡す]を[デコレータ関数に関数を渡す]に変更しました。
[デコレータ関数に引数を渡す]を新しく記載しました。

環境

  • python:3.6.5

デコレータの基本

デコレータとは

ある関数を拡張する機能で関数の開始前や終了後などに処理を付け加えることができる機能。
例えば、関数Aがあるとして、関数Aの前と後に好きな処理を追加することができます。

デコレータの形式

ある関数の開始前や終了後に実行する処理を書いた関数をデコレータ用の関数として作成して、ある関数の前に@デコレータ用関数名をつけることで適用します。

具体例

具体例とその結果は次のようになります。
詳しい説明は関数のデコレータの項目でします。

main.py
def my_decorator(my_func):
    def decorator_wrapper(*args, **kwargs):
        print('decorator before my_func')
        my_func(*args, **kwargs)
        print('decorator after my_func')

    return decorator_wrapper

@my_decorator
def print_hello():
    print('my function hello world')

print_hello()

結果

# python3 main.py
decorator before my_func
my function hello world
decorator after my_func

シンプルなデコレータ

シンプルなデコレータの例

上の具体例と同じです。

main.py
def my_decorator(my_func):
    def decorator_wrapper(*args, **kwargs):
        print('--- decorator before my_func ---')
        my_func(*args, **kwargs)
        print('--- decorator after my_func ---\n')

    return decorator_wrapper

@my_decorator
def print_hello():
    print('my function hello world')

print_hello()

結果

# python3 main.py
decorator before my_func
my function hello world
decorator after my_func

シンプルなデコレータの説明

具体例の上から順に説明していきます。
def my_decorator(my_func): がデコレータ用の関数です。引数のmy_funcにデコレータを適用した関数が渡されます。具体例の場合は、def print_hello()
次のdef decorator_wrapper(*args, **kwargs):は、実際に拡張する処理を書く場所になります。今回はシンプルに標準出力の後に、my_func(*args, **kwargs)で引数で渡された関数を実行して、最後にもう一度標準出力をしています。
実際に拡張する処理を書いた関数を返却してデコレータ用の関数が作成できます。
関数(print_hello())にこのデコレータを適用する場合は、適用したい関数の前に先ほど作成した関数を@の後に記載します。
最後に適用した関数print_hello()を実行するとdecorator_wrapperの処理通り標準出力、関数の実行、標準出力されます。

引数を渡す関数のデコレータ

引数を渡す関数のデコレータの例

適用した関数の引数がデコレータ内でどう扱われるかを試す例です。

main.py
def my_decorator(my_func):
    def decorator_wrapper(*args, **kwargs):
        print('--- decorator before my_func ---')
        print('args:{}'.format(args))
        print('kwargs:{}'.format(kwargs))
        my_func(*args, **kwargs)
        print('--- decorator after my_func ---\n')

    return decorator_wrapper

@my_decorator
def print_hello(a, b):
    print('my function hello world')

print_hello(1, 2)

print_hello(a=1, b=2)

結果

# python3 main.py
--- decorator before my_func ---
args:(1, 2)
kwargs:{}
my function hello world
--- decorator after my_func ---

--- decorator before my_func ---
args:()
kwargs:{'a': 1, 'b': 2}
my function hello world
--- decorator after my_func ---

引数を渡す関数のデコレータの説明

普通の関数と同様に適用した関数 print_hello(a, b)に引数を追加するだけでデコレータ内でも引数を使用することができます。
print_hello()の呼び方の違いでデコーダに伝わる引数も変わります。
print_hello(1, 2)のように位置で引数を与えるとdef decorator_wrapper(*args, **kwargs):argsに与えた引数が入ります。print_hello(a=1, b=2)のようにキーワード引数で与えるとkwargsに与えた引数が入ります。
デコーダ内でmy_func(*args, **kwargs)に与える引数を変えると実際にprint_hello(a, b)のaとbに入る内容が変わります。

デコレータ関数に関数を渡す

デコレータ関数に関数を渡す例

デコレータ自体に関数を渡す方法を試してみたら関数を渡せたのでその例です。

main.py
def my_decorator(my_func, *args, **kwargs):
    print('--- my_decorator before decorator_wrapper ---')
    print('args:{}'.format(args))
    print('kwargs:{}'.format(kwargs))
    def decorator_wrapper(*args, **kwargs):
        print('--- decorator before my_func ---')
        my_func()
        print('--- decorator after my_func ---\n')
        return my_func
    print('--- my_decorator after decorator_wrapper ---')
    return decorator_wrapper

def print_hello():
    print('my function hello world')

@my_decorator(print_hello, 1, a=2)
def print_hello_2():
    print('my function hello world2')


print_hello_2()

結果

# python3 main.py
--- my_decorator before decorator_wrapper ---
args:(1,)
kwargs:{'a': 2}
--- my_decorator after decorator_wrapper ---
--- decorator before my_func ---
my function hello world
--- decorator after my_func ---

my function hello world

デコレータ関数に関数を渡す説明

今回はデコレータ自体に引数を渡したいため、デコレータ用の関数def my_decorator(my_func, *args, **kwargs):にargsとkwargsを追加します。
その後、デコレータを適用する際に@my_decorator(print_hello, 1, a=2)のようにデコレータに引数を与えます。これも上の例と同じで位置による引数はargsに、キーワードによる引数はkwargsに入っています。
my_funcに実行するべき関数を入れている点に注意してください。~~本当は自分の関数オブジェクトを渡せたらよかったのですが、やり方がわかりませんでした。~~調べたらありました。直接引数で渡す方法はわかりませんでしたが、関数を増やしたら一応関数をわたせました。
上の別関数を渡す方法も残しておきます。あんまりないと思いますが、古い関数を使用させないために新しい関数をデコレータに設定することで呼ばせないなどこの方法は使えると思います。

デコレータ関数に引数を渡す

デコレータ関数に引数を渡す例

デコレータ自体に関数を渡した方法を試す例です。

main.py
def my_decorator(*args, **kwargs):
    def func_decorator(my_func):
        print('--- my_decorator before decorator_wrapper ---')
        print('args:{}'.format(args))
        print('kwargs:{}'.format(kwargs))
        def decorator_wrapper(*args, **kwargs):
            print('--- decorator before my_func ---')
            print('args:{}'.format(args))
            print('kwargs:{}'.format(kwargs))
            my_func(*args, **kwargs)
            print('--- decorator after my_func ---\n')        
        print('--- my_decorator after decorator_wrapper ---')
        return decorator_wrapper
    return func_decorator

@my_decorator(1, a=2)
def print_hello(item1, item2):
    print('my function hello world')

print_hello('a', item2=True)

結果

# python3 main.py
--- my_decorator before decorator_wrapper ---
args:(1,)
kwargs:{'a': 2}
--- my_decorator after decorator_wrapper ---
--- decorator before my_func ---
args:('a',)
kwargs:{'item2': True}
my function hello world
--- decorator after my_func ---

デコレータ関数に引数を渡す説明

デコレータ用関数def my_decorator(*args, **kwargs):にargsとkwargsを追加して、関数を渡す用関数def func_decorator(my_func):、処理を書いた関数def decorator_wrapper(*args, **kwargs):を書きます。
デコレータを適用する際に@my_decorator(1, a=2)のようにデコレータに引数を与えます。これも上の例と同じで位置による引数はargsに、キーワードによる引数はkwargsに入っています。

色々な使い方

JSONからvalueのみ抜き出して使用する

デコレータにJSON文字列を解析して、その値を抜き出して関数に渡す処理を追加しています。
実際の関数は引数にjsonの値がそのまま入っているため、そのまま使えます。

main.py
import json

def my_decorator(my_func):
    def decorator_wrapper(*args, **kwargs):
        print('--- decorator before my_func ---')
        json_dict = json.loads(args[0])
        args = json_dict.values()
        my_func(*args, **kwargs)
        print('--- decorator after my_func ---\n')

    return decorator_wrapper

@my_decorator
def print_hello(a, b=None, c=None):
    print('my function hello world')
    print('a:{}, b:{}, c:{}'.format(a, b, c))

print_hello(json.dumps({'a':1, 'b':2, 'c':10}))

結果

# python3 main.py
--- decorator before my_func ---
a:1, b:2, c:10
my function hello world
--- decorator after my_func ---

JSONからvalueのみ抜き出して使用する

必ず引数は文字列にしてほしい場合を想定して、デコレータに引数を文字列にする処理を入れました。
それによって、関数に来る引数は必ず文字列になっています。

main.py
def my_decorator(my_func):
    def decorator_wrapper(*args, **kwargs):
        print('--- decorator before my_func ---')
        args = [str(i) for i in args]
        my_func(*args, **kwargs)
        print('--- decorator after my_func ---\n')

    return decorator_wrapper

@my_decorator
def print_hello(a, b=None, c=None):
    print('my function hello world')
    print('a:{}, b:{}, c:{}'.format(type(a), b, c))

print_hello(1, 2, 3)

結果

# python3 main.py
--- decorator before my_func ---
my function hello world
a:<class 'str'>, b:2, c:3
--- decorator after my_func ---

関数実行時にリクエストを発行してレスポンスを引数に入れる

デコレータにリクエストの発行とレスポンスの解析を入れて、その値を関数に渡す処理を追加しています。
今回は手間なのでGETしか作っていませんが、ちゃんと作ればリクエストがわからない人に対しても容易にリクエストを使用することができます。
余り慣れていない人にとっては、リクエストを使用するのはかなりハードルが高いのでだいぶ助かるかなと思います。

main.py
import requests

def request_decorator(*deco_args, **deco_kwards):
    def func_decorator(my_func):
        def decorator_wrapper(*args, **kwargs):            
            method = deco_kwards['method']
            url = deco_args[0].format(**kwargs)
            if method == 'GET':
                response = requests.get(url)
                
            my_func(*args, **kwargs, response_json=response.json())
        return decorator_wrapper
    return func_decorator

@request_decorator('http://127.0.0.1:5000/sample/{message}', method='GET')
def sample_request(message, response_json=None):
    print(response_json)

sample_request(message='mess')

結果

# python3 main.py
 {'abc': 12, 'bcd': 55, 'message': 'mess'}  

おわりに

関数のデコレータを色々見ていきました。
ぱっと見は何をしているのか分かり難いため取っつきにくいですが、応用すれば色々なことができました。
記載した内容以外にもjsonのチェックであったり、ファイル名を与えてファイルの中身を引数として引数として与えるなど多くのことができそうです。
さらにクラスをデコレータ化することでさらに色々なことができるようになります。~~クラスのデコレータ化については次回まとめます。~~まとめました。pythonのクラスをデコレータ(@関数)で使用する

3
13
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
3
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?