Подтвердить что ты не робот

Можно ли объединить два декоратора в один на Python?

Есть ли способ объединить два декоратора в один новый декоратор в python?

Я понимаю, что могу просто применить несколько декораторов к функции, но мне было любопытно, есть ли простой способ объединить два в новый.

4b9b3361

Ответ 1

Немного более общий:

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

Ответ 2

Да. См. Определение декоратора здесь.

Что-то вроде этого должно работать:

def multiple_decorators(func):
   return decorator1(decorator2(func))

@multiple_decorators
def foo(): pass

Ответ 3

Если декораторы не принимают дополнительных аргументов, вы можете использовать

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

Ответ 4

Декораторы - это просто функции, которые принимают функцию в качестве входных данных и возвращают новую функцию. Это:

@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():
    ...

Ответ 5

Если вы не хотите повторяться в тестовом наборе, вы можете сделать так:

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)

Ответ 6

И расширить ответ @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

В этом примере вы можете создать новый декоратор, который содержит несколько декораторов.