考虑一个非常简单的功能:
Embedded Binaries
其输出基本上是我要创建的对象的实例,或者是该函数无法创建该对象的异常。我们可以说输出是 binary ,因为它要么成功(并返回一个对象),要么不成功(并返回一个Exception)。
处理第三个状态的最Python方式是什么,即“成功但有一些警告”?
def generate_something(data):
if data is None:
raise Exception('No data!')
return MyObject(data)
返回元组是处理此问题的唯一方法,还是可以从函数内广播或产生警告并从调用者那里捕获警告?
答案 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
...