Pythonで2つのデコレータを1つの新しいデコレータに結合する方法はありますか?
関数に複数のデコレータを適用するだけでよいことに気付きましたが、2つを組み合わせて新しいものにする簡単な方法があるかどうかに興味がありました。
もう少し一般的:
def composed(*decs):
def deco(f):
for dec in reversed(decs):
f = dec(f)
return f
return deco
次に
@composed(dec1, dec2)
def some(f):
pass
と同等です
@dec1
@dec2
def some(f):
pass
はい。デコレータの定義を参照してください ここ 。
このようなものが機能するはずです:
def multiple_decorators(func):
return decorator1(decorator2(func))
@multiple_decorators
def foo(): pass
デコレータは、関数を入力として受け取り、新しい関数を返す単なる関数です。この:
@deco
def foo():
...
これと同等です:
def foo():
...
foo = deco(foo)
つまり、decorated関数(foo
)が引数としてデコレータに渡され、foo
がデコレータの戻り値に置き換えられます。この知識があれば、他の2つのデコレータを組み合わせたデコレータを簡単に作成できます。
def merged_decorator(func):
return decorator2(decorator1(func))
# now both of these function definitions are equivalent:
@decorator2
@decorator1
def foo():
...
@merged_decorator
def foo():
...
デコレータが次の2つのような引数を受け入れると、少し注意が必要になります。
@deco_with_args2(bar='bar')
@deco_with_args1('baz')
def foo():
...
これらのデコレータがどのように実装されているのか不思議に思うかもしれません。実際には非常に簡単です:deco_with_args1
およびdeco_with_args2
はanother関数デコレータを返す関数です。引数付きのデコレータは基本的にデコレータファクトリです。これに相当するもの:
@deco_with_args('baz')
def foo():
...
これは:
def foo():
...
real_decorator = deco_with_args('baz')
foo = real_decorator(foo)
引数を受け入れてから他の2つのデコレータを適用するデコレータを作成するには、独自のデコレータファクトリを実装する必要があります。
def merged_decorator_with_args(bar, baz):
# pass the arguments to the decorator factories and
# obtain the actual decorators
deco2 = deco_with_args2(bar=bar)
deco1 = deco_with_args1(baz)
# create a function decorator that applies the two
# decorators we just created
def real_decorator(func):
return deco2(deco1(func))
return real_decorator
このデコレータは、次のように使用できます。
@merged_decorator_with_args('bar', 'baz')
def foo():
...
デコレータが追加の引数を取らない場合は、
def compose(f, g):
return lambda x: f(g(x))
combined_decorator = compose(decorator1, decorator2)
今
@combined_decorator
def f():
pass
と同等になります
@decorator1
@decorator2
def f():
pass
テストスイートで自分自身を繰り返したくない場合は、次のようにすることができます::
def apply_patches(func):
@functools.wraps(func)
@mock.patch('foo.settings.USE_FAKE_CONNECTION', False)
@mock.patch('foo.settings.DATABASE_URI', 'li://foo')
@mock.patch('foo.connection.api.Session.post', autospec=True)
def _(*args, **kwargs):
return func(*args, **kwargs)
return _
これで、各関数の上にある大量のデコレータの代わりに、テストスイートでそれを使用できます。
def ChuckNorrisCase(unittest.TestCase):
@apply_patches
def test_chuck_pwns_none(self):
self.assertTrue(None)
そして@Jochenの答えを拡張するには:
import click
def composed(*decs):
def deco(f):
for dec in reversed(decs):
f = dec(f)
return f
return deco
def click_multi(func):
return composed(
click.option('--xxx', is_flag=True, help='Some X help'),
click.option('--zzz', is_flag=True, help='Some Z help')
)(func)
@click_multi
def some_command(**args):
pass
この例では、複数のデコレータを含む新しいデコレータを作成できます。