Декоратор с аргументами: избегайте скобок, когда нет аргументов - PullRequest
0 голосов
/ 01 сентября 2018

Ниже мой @logged() производитель декораторов. Вот как это работает примерно:

  1. Он принимает экземпляр logger и флаг disabled.
  2. Если disabled равно False, выводятся некоторые журналы до / после оформленной функции.
  3. Если disabled равен True, он ничего не выводит, а также подавляет logger для декорированной функции.

Оба аргумента logger и disabled имеют значения по умолчанию. Однако, когда я хочу использовать значения по умолчанию, мне все равно нужно написать пустые скобки, например, так:

@logged()
def foo():
    pass

Есть ли способ избавиться от этих пустых скобок, когда я просто хочу аргументы по умолчанию? Вот пример того, что я хотел бы получить:

@logged
def foo():
    pass

@logged(disabled=True)
def bar():
    pass

код производителя @logged() декоратора:

import logging
import logging.config

from functools import wraps

def logged(logger=logging.getLogger('default'), disabled=False):
    '''
    Create a configured decorator that controls logging output of a function

    :param logger: the logger to send output to
    :param disabled: True if the logger should be disabled, False otherwise
    '''

    def logged_decorator(foo):
        '''
        Decorate a function and surround its call with enter/leave logs

        Produce logging output of the form:
        > enter foo
          ...
        > leave foo (returned value)
        '''

        @wraps(foo)
        def wrapper(*args, **kwargs):

            was_disabled = logger.disabled

            # If the logger was not already disabled by something else, see if
            # it should be disabled by us. Important effect: if foo uses the
            # same logger, then any inner logging will be disabled as well.
            if not was_disabled:
                logger.disabled = disabled

            logger.debug(f'enter {foo.__qualname__}')

            result = foo(*args, **kwargs)

            logger.debug(f'leave {foo.__qualname__} ({result})')

            # Restore previous logger state:
            logger.disabled = was_disabled

            return result

        return wrapper

    return logged_decorator

logging.config.dictConfig({
    'version': 1,
    'formatters': {
        'verbose': {
            'format': '%(asctime)22s %(levelname)7s %(module)10s %(process)6d %(thread)15d %(message)s'
        }
        , 'simple': {
            'format': '%(levelname)s %(message)s'
        }
    }
    , 'handlers': {
        'console': {
            'level': 'DEBUG'
            , 'class': 'logging.StreamHandler'
            , 'formatter': 'verbose'
        }
    },
    'loggers': {
        'default': {
            'handlers': ['console']
            , 'level': 'DEBUG',
        }
    }
})

@logged()
def foo():
    pass

if __name__ == '__main__':
    foo()

Ответы [ 2 ]

0 голосов
/ 11 марта 2019

Я был невероятно раздражен этим и в итоге написал библиотеку для решения этой проблемы: decopatch .

Он поддерживает два стиля разработки: вложенный (как на фабриках Python Decorator) и плоский (на один уровень вложенности меньше). Вот как ваш пример будет реализован в плоском режиме:

from decopatch import function_decorator, DECORATED
from makefun import wraps

@function_decorator
def logged(disabled=False, logger=logging.getLogger('default'), func=DECORATED):

    # (1) create a signature-preserving wrapper
    @wraps(func)
    def _func_wrapper(*f_args, **f_kwargs):
        # stuff
        result = func(*f_args, **f_kwargs)
        # stuff
        return result

    # (2) return it
    return _func_wrapper

Обратите внимание, что я использую makefun.wraps вместо functools.wraps, чтобы подпись полностью сохранялась (оболочка вообще не вызывается, если аргументы неверны).

decopatch поддерживает дополнительный стиль разработки, который я называю double-flat , который предназначен для создания оболочек с функцией сохранения подписи, подобных этой. Ваш пример будет реализован так:

from decopatch import function_decorator, WRAPPED, F_ARGS, F_KWARGS

@function_decorator
def logged(disabled=False, logger=logging.getLogger('default'), 
           func=WRAPPED, f_args=F_ARGS, f_kwargs=F_KWARGS):
    # this is directly the signature-preserving wrapper
    # stuff
    result = func(*f_args, **f_kwargs)
    # stuff
    return result

Вы можете проверить, что оба стиля работают должным образом:

@logged(disabled=True)
def foo():
    pass

@logged
def bar():
    pass

foo()

bar()

Пожалуйста, проверьте документацию для деталей.

0 голосов
/ 01 сентября 2018

Вы можете использовать if-else внутри тела декоратора:

def logged(func=None, *, disabled=False, logger=logging.default()):
    def logged_decorator(func):
        # stuff
        def wrapper(*args_, **kwargs):
            # stuff
            result = func(*args_, **kwargs)
            # stuff 
            return result
        return wrapper
    if func:
        return logged_decorator(func)
    else:
        return logged_decorator

У (func=None, *, logger=..., disabled=False) есть звездочка, обозначающая последние 2 аргумента как аргументы только для ключевых слов, так как все остальные аргументы, кроме func, распаковываются в *, который в этом случае не имел идентификатора, поэтому фактически «потеряны». Это означает, что вы должны использовать ключевые аргументы при обычном использовании декоратора:

@logged(
    disabled=True,
    logged=logging.logger # ...
)
def foo(): pass

Или ...

@logged
def bar(): pass

Смотрите здесь: Как создать декоратор с необязательными параметрами?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...