LoginSignup
7
6

More than 5 years have passed since last update.

Pythonで、動的に引数を受け付けるデコレータを作るデコレータを作る

Last updated at Posted at 2017-01-03

背景

Pythonで独自にデコレータを定義して利用していると、後からデコレータに引数を渡せるようにしたくなる場合がある。

元々のデコレータ利用箇所
@my_decorator
def hoge():
    # ...

一般的なデコレータの実装をした場合、後からデコレータに引数を渡せるようにしようと思うと、
↑のような使い方をしていたデコレータを全て↓のように書き換える必要が出てくる。

デコレータ利用時に引数を設定する場合
@my_decorator(arg1='value1')
def hoge():
    # ...

引数が不要な場合でも、デコレータの実装が変わってしまうと括弧をつける必要がある。

デコレータ利用時に引数を設定しない場合
@my_decorator()
def hoge():
    # ...

デコレータを利用している全ての箇所を書き換えるのは修正も検証も面倒なので、引数が不要な場合は括弧がなくても動くようにしたいと思った。

目的

以下のように、動的に引数を利用できるデコレータ(my_decorator)を作りたい。
ついでにそのような機能を汎用的に使えるように、デコレータ用のデコレータとして実装したい。

# 引数なしの場合
@my_decorator
def hoge():
    pass

# 引数ありの場合
@my_decorator(arg1='value1')
def fuga():
    pass

実装

以下のように、デコレータに対して適用するための、引数を動的に解釈するデコレータを実装する。

decorators.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import functools


def dynamic_args(func0):
    def wrapper(*args, **kwargs):
        if len(args) != 0 and callable(args[0]):
            # 第一引数に関数が渡された場合: 引数なしのデコレータとして扱う
            func = args[0]
            return functools.wraps(func)(func0(func))
        else:
            # その他: 引数ありのデコレータとして扱う
            def _wrapper(func):
                return functools.wraps(func)(func0(func, *args, **kwargs))
            return _wrapper
    return wrapper

使い方イメージ

上記のようなデコレータを用意しておけば、自分のデコレータは以下のように定義するだけで目的が達成できる。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from decorators import dynamic_args


# 上で作ったデコレータを、自分のデコレータに適用する
@dynamic_args
def my_decorator(func, arg1='default_value1', arg2='default_value2'):
    def wrapper(*args, **kwargs):
        # 定義した引数(arg1, arg2)を利用して実際にさせたい処理を書く
        # ...
    return wrapper

利用サンプル

サンプルとして、返り値(文字列)の前後に括弧をつけるデコレータをmy_decoratorという名前で作成する。

sample.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from decorators import dynamic_args


#####################
# 自前のデコレータ定義 #
#####################
@dynamic_args
def my_decorator(func, before='「', after='」'):
    def wrapper(*args, **kwargs):
        return "{} {} {}".format(before, func(*args, **kwargs), after)
    return wrapper


##########################
# 引数なしのデコレータ利用例 #
##########################
@my_decorator
def func_without_args():
    return "引数なしデコレータ"

print(func_without_args()) # output: 「 引数なしデコレータ 」


##########################
# 引数ありのデコレータ利用例 #
##########################
@my_decorator(before='『', after='』')
def func_with_args():
    return "引数ありデコレータ"

print(func_with_args()) # output: 『 引数ありデコレータ 』


####################################
# 定義済みの関数にデコレータを適用する例 #
####################################
def func_without_decorator():
    return "デコレータ無し"


print(my_decorator(func_without_decorator)()) # output: 「 デコレータ無し 」
print(my_decorator(before="『", after="』")(func_without_decorator)()) # output: 『 デコレータ無し 』

参考

Python Tips:デコレータに引数を渡したい - Life with Python
http://www.lifewithpython.com/2016/09/python-decorator-with-arguments.html

Python のデコレータ式 (2) - デコレータに引数があり、複数のデコレータを適用する場合 | すぐに忘れる脳みそのためのメモ
http://jutememo.blogspot.jp/2008/10/python-2.html

python - AttributeError: 'str' object has no attribute 'module' - Stack Overflow
http://stackoverflow.com/questions/18493879/attributeerror-str-object-has-no-attribute-module

7
6
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
7
6