从函数收集警告的大多数pythonic方法

时间:2019-04-30 09:06:09

标签: python python-3.x

考虑一个非常简单的功能:

Embedded Binaries

其输出基本上是我要创建的对象的实例,或者是该函数无法创建该对象的异常。我们可以说输出是 binary ,因为它要么成功(并返回一个对象),要么不成功(并返回一个Exception)。

处理第三个状态的最Python方式是什么,即“成功但有一些警告”?

def generate_something(data):
    if data is None:
        raise Exception('No data!')

    return MyObject(data)

返回元组是处理此问题的唯一方法,还是可以从函数内广播或产生警告并从调用者那里捕获警告?

2 个答案:

答案 0 :(得分:3)

内置选项:warnings

Python在warnings模块中实现了内置的警告机制。问题在于warnings维护了一个全局warnings filter,这可能会无意间导致函数抛出的警告被抑制。这是问题的证明:

import warnings

def my_func():
    warnings.warn('warning!')

my_func()  # prints "warning!"

warnings.simplefilter("ignore")
my_func()  # prints nothing

如果无论如何要使用warnings,都可以使用warnings.catch_warnings(record=True)收集列表中所有抛出的警告:

with warnings.catch_warnings(record=True) as warning_list:
    warnings.warn('warning 3')

print(warning_list)  # output: [<warnings.WarningMessage object at 0x7fd5f2f484e0>]

自制选项

由于上述原因,建议您改用自己的警告机制。有多种实现方法:

  • 只需返回警告列表

    开销最小的最简单解决方案:只需返回警告即可。

    def example_func():
        warnings = []
    
        if ...:
            warnings.append('warning!')
    
        return result, warnings
    
    result, warnings = example_func()
    for warning in warnings:
        ...  # handle warnings
    
  • 将警告处理程序传递给函数

    如果要在警告生成时立即处理警告,可以重写函数以接受警告处理程序作为参数:

    def example_func(warning_handler=lambda w: None):
        if ...:
            warning_handler('warning!')
    
        return result
    
    
    def my_handler(w):
        print('warning', repr(w), 'was produced')
    
    result = example_func(my_handler)
    
  • contextvars(python 3.7 +)

    使用python 3.7,我们获得了contextvars模块,该模块使我们可以基于上下文管理器实现更高级别的警告机制:

    import contextlib
    import contextvars
    import warnings
    
    
    def default_handler(warning):
        warnings.warn(warning, stacklevel=3)
    
    _warning_handler = contextvars.ContextVar('warning_handler', default=default_handler)
    
    
    def warn(msg):
        _warning_handler.get()(msg)
    
    
    @contextlib.contextmanager
    def warning_handler(handler):
        token = _warning_handler.set(handler)
        yield
        _warning_handler.reset(token)
    

    用法示例:

    def my_warning_handler(w):
        print('warning', repr(w), 'was produced')
    
    with warning_handler(my_warning_handler):
        warn('some problem idk')  # prints "warning 'some problem idk' was produced"
    
    warn(Warning('another problem'))  # prints "Warning: another problem"
    

    注意事项::到目前为止,contextvars不支持生成器。 (相关 PEP。)类似以下示例的内容将无法正常工作:

    def gen(x):
        with warning_handler(x):
            for _ in range(2):
                warn('warning!')
                yield
    
    g1 = gen(lambda w: print('handler 1'))
    g2 = gen(lambda w: print('handler 2'))
    
    next(g1)  # prints "handler 1"
    next(g2)  # prints "handler 2"
    next(g1)  # prints "handler 2"
    
  • 没有contextvars(对于python <3.7)

    如果您没有contextvars,则可以改用这种异步不安全的实现:

    import contextlib
    import threading
    import warnings
    
    
    def default_handler(warning):
        warnings.warn(warning, stacklevel=3)
    
    _local_storage = threading.local()
    _local_storage.warning_handler = default_handler
    
    
    def _get_handler():
        try:
            return _local_storage.warning_handler
        except AttributeError:
            return default_handler
    
    
    def warn(msg):
        handler = _get_handler()
        handler(msg)
    
    
    @contextlib.contextmanager
    def warning_handler(handler):
        previous_handler = _get_handler()
        _local_storage.warning_handler = handler
    
        yield
    
        _local_storage.warning_handler = previous_handler
    

答案 1 :(得分:0)

您可以返回包含问题的Exceptions(或自定义例外)列表,以便以后处理:

class MyWarning(Warning):
    pass

def generate_something(data):
    warnings = []
    if data is None:
        raise Exception("No data!")

    if data.value_1 == 2:
        warnings.append(MyWarnin('Hmm, value_1 is 2'))    

    if data.value_2 == 1:
        warnings.append(MyWarning('Hmm, value_2 is 1'))    

    return MyObject(data), warnings

然后例如:

def handle_warnings(warnings):
    for w in warnings:
        try:
            raise w
        except MyWarning:
            ...
        except AttributeError: #in case you want to handle other type of errors
            ...