首页 > 解决方案 > 装饰器中基于 Python 点击​​选项的登录

问题描述

新的开始。我有一个 CLI 应用程序,它使用 click 来处理参数解析。对于主要的“可执行”脚本,我有一个定义的详细程度标志(-v、-vv、-vvv、...)来控制日志记录的详细程度。我想“跟踪”特定函数的函数调用。下面是一个示例,希望可以清楚地说明。

import click
import logging
import functools


class MyLogger(object):
    def __init__(self):
        self.__logger = None

    def init_logger(self, name, verbosity):
        logging_levels = {0: logging.CRITICAL,
                          1: logging.ERROR,
                          2: logging.INFO,
                          3: logging.DEBUG}
        logging.basicConfig(level=logging_levels.get(verbosity, logging.WARNING))
        self.__logger = logging.getLogger(name)

    @property
    def logger(self):
        return self.__logger


myLogger = MyLogger()


class TraceFunction(object):
    def __init__(self, logger):
        self.logger = logger

    def __call__(self, function):
        name = function.__name__

        @functools.wraps(function)
        def wrapped(*args, **kwargs):
            self.logger.debug(f'{name}({list(*args)}, {dict(**kwargs)})')
            result = function(*args, **kwargs)
            self.logger.debug(f'{result}')
            return result

        return wrapped


# (1) @TraceFunction(myLogger.logger)
def echo(message):
    return message.upper()


@click.command('echo')
@click.option('-e', '--echo', 'message', required=True, type=str)
def echo_command(message):
    myLogger.logger.info('echo_command')
    return echo(message)


@click.group()
@click.option('-v', 'verbosity', count=True)
def main(verbosity: int):
    myLogger.init_logger(__name__, verbosity)
    # (2) TraceFunction(myLogger.logger)(echo)
    myLogger.logger.info('main')


if __name__ == '__main__':
    main.add_command(echo_command)
    main()

以上如果执行将正确产生以下输出:

script.py -vv echo -e "Hello World"
INFO: __main__:main
INFO: __main__:echo_command

我想“追踪”这个功能:echo。更准确地说,我想用实际参数和返回值记录实际的函数调用。好的,不止于此,但我需要一个最小的样本。为此,我尝试了两件事,在评论中标有 (1) 和 (2)。

@TraceFunction(myLogger.logger)
def echo(message):
    return message.upper()

它完全不起作用,因为我最初的问题 python 将执行 TraceFunction。在“main”中调用(echo)之前我调用了init_logger,它本质上会配置记录器本身。作为 TraceFunction 的结果。调用记录器是无,我得到:

AttributeError: 'NoneType' object has no attribute 'debug'

好吧,我以后可以注册它,至少我想用(2)。好吧,异常肯定消失了,但是从不调用调用中定义的“包装”,并且除了已经显示的内容之外,再一次没有记录任何内容

script.py -vvv echo -e "Hello World"
INFO: __main__:main
INFO: __main__:echo_command

@Update 按照afterburner的回答,事情走得更远了,但它没有做它应该做的事情:

script.py -vvv echo -e "Hello World"
DEBUG:__main__:echo(['F','o','o'],{})
DEBUG:__main__:FOO
INFO: __main__:main
INFO: __main__:echo_command

预计哪个井。另一方面,预期的输出将是:

script.py -vvv echo -e "Hello World"
INFO: __main__:main
INFO: __main__:echo_command
DEBUG:__main__:echo(['Hello World'],{})
DEBUG:__main__:HELLO WORLD

标签: python-3.xpython-decoratorspython-clickpython-logging

解决方案


我设法让它工作,但它确实很丑……至少在目前的形式下。唯一需要的改变是:

@click.group()
@click.option('-v', 'verbosity', count=True)
def main(verbosity: int = 0):
    global echo

    myLogger.init_logger(__name__, verbosity)
    echo = TraceFunction(myLogger.logger)(echo)  # <<< !!!
    myLogger.logger.info('main')

这样做的输出变为:

INFO:__main__:main
INFO:__main__:echo_command
TRACE:__main__:echo(['Hello World'], {})
TRACE:__main__:HELLO WORLD

所以答案是,我完全错过了:

TraceFunction(myLogger.logger)(echo)

很好,但我需要将它分配给原始的 echo 函数:

echo = TraceFunction(myLogger.logger)(echo)

推荐阅读