Python - 情景管理器(@contextmanager)

有这样一个函数,它的默认日志级别是WARNING,所以只会把大于等于WARNING 级别的日志打印出来

def my_function():
    logging.debug('Some debug data')
    logging.error('Error log here')
    logging.debug('More debug data')

要想临时改变日志级别,可以定义情景管理器(context manager)。用@contextmanager来修饰下面这个辅助函数,就能定义出这样一种管理器。
这种管理器可以用在with语句里面,让日志记录器(Logger)在进入这个范围之后,临时改变自己的日志级别,这样的话,原来那些低级别的消息
现在就可以显示出来了,待with语句执行完毕后,再恢复原有的日志级别。

@contextmanager
def debug_logging(level):
    logger = logging.getLogger()
    old_level = logger.getEffectiveLevel()  # 获得当前的日志级别
    logger.setLevel(level)  
    try:
        yield
    finally:
        logger.setLevel(old_level)

if __name__ == '__main__':
    with debug_logging(logging.DEBUG):
        print('* Inside:')
        my_function()
    print('* after:')
    my_function()


'''
* Inside:
DEBUG:root:Some debug data
ERROR:root:Error log here 
DEBUG:root:More debug data
* after:                  
ERROR:root:Error log here 
'''

运行过程:
系统开始执行with语句时,会先把@contextmanager 所修饰的debug_logging辅助函数推进都yield表达式所在的地方,然后开始执行with结构的主体部分。如果执行with语句块的过程种发生异常,那么这个异常会重新抛出到yield表达式所在的那一行里,从而为辅助函数的try结构所捕获

带目标的with语句

with 语句还有依照那个写法,叫做with...as...,它可以把情景管理器所返回的对象赋值给右侧的局部变量,这样的话,with结构的主题部分代码就可以通过这个局部变量与情景管理器所针对的那套情景交互了

@contextmanager
def log_level(level,name):
    logger = logging.getLogger(name)
    handler = logging.StreamHandler()
    formatter = logging.Formatter('%(name)s - %(message)s')
    handler.setFormatter(formatter)
    logger.addHandler(handler)

    old_level = logger.getEffectiveLevel()
    logger.setLevel(level)
    try:
        yield logger
    finally:
        logger.setLevel(old_level)

if __name__ == '__main__':
    with log_level(logging.DEBUG,'my-log') as logger:
        logger.debug(f'This is a message for {logger.name}')
        logging.debug('This will not print')  # 不会输出,日志级别时WARNING

# out:
'''
my-log - This is a message for my-log
'''

退出with 机构后如果在通过名叫'my-log'的这个Logger调用debug 方法来记录DEBUG级别的消息,那么这些消息就显示不出来了,因为这个Logger的日志级别这时已经恢复到之前默认的水平

    with log_level(logging.DEBUG,'my-log') as logger:
        # print(logger,logger.getEffectiveLevel)
        logger.debug(f'This is a message for {logger.name}')
        logging.debug('This will not print')

    logger = logging.getLogger('my-log')
    logger.debug('Debug will not print')
    logger.error('Error will print')

# out:
'''
my-log - This is a message for my-log
my-log - Error will print
ERROR:my-log:Error will print
'''

总结:

  • 可以把try/finally 逻辑封装到情景管理器里面,这样就能通过with结构反复运用这套逻辑,而不需要每次用到的时候,都手工打一遍代码
  • Python 内置的contextlb 模块提供了contextmanger修饰器,让我们可以很方便地修饰某个函数,从而制作出相对应的情景管理器,使得这个函数能够运用在with语句里
  • 情景管理器通过yeild的产生的值,可以由with语句之中位于as右侧的那个变量所接收,这样的话,我们就可以通过该变量与当前情景交互了
posted @ 2023-01-02 11:35  chuangzhou  阅读(105)  评论(0编辑  收藏  举报