Есть ли способ объединить два декоратора в один новый декоратор в python?
Я понимаю, что могу просто применить несколько декораторов к функции, но мне было любопытно, есть ли простой способ объединить два в новый.
Есть ли способ объединить два декоратора в один новый декоратор в python?
Я понимаю, что могу просто применить несколько декораторов к функции, но мне было любопытно, есть ли простой способ объединить два в новый.
Немного более общий:
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
Если декораторы не принимают дополнительных аргументов, вы можете использовать
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
Декораторы - это просто функции, которые принимают функцию в качестве входных данных и возвращают новую функцию. Это:
@deco
def foo():
...
Это эквивалентно этому:
def foo():
...
foo = deco(foo)
Другими словами, декорированная функция (foo
) передается в качестве аргумента декоратору, а затем foo
заменяется возвращаемым значением декоратора. Обладая этими знаниями, легко написать декоратор, который объединяет два других декоратора:
def merged_decorator(func):
return decorator2(decorator1(func))
# now both of these function definitions are equivalent:
@decorator2
@decorator1
def foo():
...
@merged_decorator
def foo():
...
Это становится немного сложнее, если декораторы принимают аргументы, как эти два:
@deco_with_args2(bar='bar')
@deco_with_args1('baz')
def foo():
...
Вы можете удивиться, как эти декораторы вообще реализованы. На самом деле все довольно просто: deco_with_args1
и deco_with_args2
- это функции, которые возвращают другую функцию-декоратор. Декораторы с аргументами - это по сути декораторские фабрики. Эквивалент этого:
@deco_with_args('baz')
def foo():
...
Это:
def foo():
...
real_decorator = deco_with_args('baz')
foo = real_decorator(foo)
Чтобы создать декоратор, который принимает аргументы, а затем применяет два других декоратора, мы должны реализовать нашу собственную фабрику декораторов:
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 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
В этом примере вы можете создать новый декоратор, который содержит несколько декораторов.