带参数的装饰器:没有参数时避免使用括号

问题描述 投票:2回答:2

下面是我的@logged()装饰制造商。以下是它的工作原理:

  1. 它接受logger实例和disabled标志。
  2. 如果disabledFalse,它会在装饰函数之前/之后输出一些日志。
  3. 如果disabledTrue,它不会输出任何东西,也会抑制装饰函数的logger

loggerdisabled参数都有默认值。但是,当我想使用默认值时,我仍然需要写空括号,如下所示:

@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()
python logging python-decorators
2个回答
4
投票

您可以在装饰器主体内部使用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)有一个星号arg表示最后两个参数作为仅限关键字的参数,因为除了func之外的任何其他参数都被解压缩到*中,在这种情况下没有标识符,所以实际上是'丢失'。这意味着在正常使用装饰器时必须使用关键字参数:

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

要么...

@logged
def bar(): pass

见这里:How to build a decorator with optional parameters?


0
投票

我非常恼火,并最终写了一个库来解决这个问题:decopatch

它支持两种开发风格:嵌套(如在python decorator工厂中)和flat(一个较少的嵌套级别)。这是您在平面模式下实现示例的方式:

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支持一种额外的开发风格,我称之为双平面,专门用于创建像这样的签名保留函数包装器。您的示例将实现如下:

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()

请查看documentation了解详情。

© www.soinside.com 2019 - 2024. All rights reserved.