Python警告在试图警告用户的事情之后出现。

5

我正在使用目标代码中的警告来提醒用户发生了某些事情,但不停止代码。下面是一个简单的模型,基于我在实际代码中遇到的更复杂的场景:

from warnings import warn
class myClass(object):        
    def __init__(self, numberArg):

        if numberArg > 1000:
            self._tmpTxt = "That's a really big number for this code." + \
                           "Code may take a while to run..."
            warn("\n%s %s" %(numberArg, self._tmpTxt))
            print("If this were real code:")
            print("Actions code takes because this is a big number would happen here.")

        print("If this were real code, it would be doing more stuff here ...")

mc1 = myClass(1001)

在我的实际代码中,当我实例化执行__init__(self, numberArg)的类时,在发出警告之后,所有后续处理都已完成。为什么会这样呢?
更重要的是,有没有办法确保先输出警告,然后再运行其余代码并提供输出?与此处提供的示例一样,期望的效果是在发生事件之前向用户发出警告,并像警告格式一样提供输出。
注意:在Windows 7环境下使用Python 2.7的iPython/Jupyter遇到了这个问题。

我喜欢warn()的行为方式。它会输出有问题的模块名称、代码行数,然后使用粉色输出您提出的警告消息格式来提醒用户出现了错误。有什么解决这个问题的方法吗?欢迎提出想法。(只是试图让我的代码更好)。请注意:关于延迟理论,虽然此示例中的代码是瞬时的,但在我的实际代码中,计算需要3分钟以上才能完成,并且警告仍将出现在最后。如果这是一种延迟,则似乎是“延迟到所有代码完成之后”的延迟,而不是基于时间的延迟。 - TMWP
1
清空 stderr 呢?那样有帮助吗? - GIZ
这或许值得一试。在调用stderr直接输出之前,我会查看warn()文档,看看是否有办法在warn或其相关函数中实现。目前正在处理代码中的一些问题。如果你有样例可以提供,那么可以节省我的时间。否则,我下一次回复可能需要等一段时间。 - TMWP
1
@TMWP 在 warn 下面加上这个调用:sys.stderr.flush(). 如果你查看 warnings 模块文档,你会发现: "决定是否发布警告消息的是警告过滤器,它是一系列匹配规则和操作。" 可能是一个过滤器问题。先尝试刷新 stderr 看看会发生什么。 - GIZ
基于GUI的IDE替换了标准流并可能改变它们的行为。在Win 10上使用2.7和3.6版本的控制台和IDLE编辑器运行代码时,警告会首先被打印出来。在IDLE中,警告是粉色的,而在控制台中则不是。如果IPython延迟stderr输出直到print语句之后,我认为这是一个需要向开发人员报告的错误。 - Terry Jan Reedy
显示剩余5条评论
1个回答

5

@direprobs在评论中提供了对这个问题最简单的答案。在调用warn()后添加这行代码。

sys.stderr.flush()

可以将此代码复制并粘贴到Python 2.7(Jupyter笔记本)中快速运行并查看效果:

实验一(与下面的代码进行比较):

# Note how warnings in this sample are held until after code is run and then output at the end ...

from warnings import warn
from warnings import resetwarnings

class myClass(object):        
    def __init__(self, numberArg):

        if numberArg > 1000:
            self._tmpTxt = "That's a really big number for this code." + \
                           "Code may take a while to run..."
            warn("\n%s %s" %(numberArg, self._tmpTxt), stacklevel=1, category=RuntimeWarning)
                                                       # possible categories (some of them):
                                                       # UserWarning, Warning, RunTimeWarning, ResourceWarning
                                                       # stacklevel was a experiment w/ no visible effect
                                                       # in this instance
            
            resetwarnings()                            # tried putting this before and after the warn()
            print("If this were real code:")
            print("Actions code takes because this is a big number would happen here.")
        
        print("If this were real code, it would be doing more stuff here ...")
        
mc1 = myClass(1001)

实验二:

# In this case, we want the warning to come before code execution.  This is easily fixed as shown below.
# note: removed some extraneous useless stuff, the line to look for is sys.stderr.flush()

from warnings import warn
from warnings import resetwarnings
import sys

class myClass(object):        
    def __init__(self, numberArg):

        if numberArg > 1000:
            self._tmpTxt = "That's a really big number for this code." + \
                           "Code may take a while to run..."
            warn("\n%s %s" %(numberArg, self._tmpTxt), category=Warning)            
            sys.stderr.flush()                         # put this after each warn() to make it output more immediately
            print("If this were real code:")
            print("Actions code takes because this is a big number would happen here.")
        
        print("If this were real code, it would be doing more stuff here ...")
        
mc1 = myClass(1001)  

实验三:

# code provided as an experiment ... may be updated later with a more useful example ...
# in theory, filterwarnings should help shake out repeat warnings if used with right arguments
#   * note how our loop causes the content to print twice, and in theory, the 3 instances of warnings
#   * occur twice each for 6 possible output warnings
#   * each new occurance (3 of them) still outputs, but when the same ones come up again, they don't
#   * we get 3 instead of 6 warnings ... this should be the effect of filterwarning("once")
#     in this instance

# help on this: https://docs.python.org/3/library/warnings.html#warning-filter
#               in this example:
#                  "once" arg = print only the first occurrence of matching warnings, regardless of location

from warnings import warn
from warnings import resetwarnings
from warnings import filterwarnings

class myClass(object):        
    def __init__(self, numberArg):
        
        for i in [1,2]:

            if numberArg > 1000:
                print("loop count %d:" %(i))
                self._tmpTxt = "That's a really big number for this code." + \
                               "Code may take a while to run..."
                filterwarnings("once")
                warn("\n%s %s" %(numberArg, self._tmpTxt), stacklevel=1, category=RuntimeWarning)
                sys.stderr.flush() # this provides warning ahead of the output instead of after it
                # resetwarnings()  # no noticeable effect on the code
                print("If this were real code:")
                print("Actions code takes because this is a big number would happen here.")

            if numberArg > 20000:
                self._tmpTxt = "That's a really really really big number for this code." + \
                               "Code may take a while to run..."                
                filterwarnings("once", "\nFW: %s %s" %(numberArg, self._tmpTxt))
                warn("\n%s %s" %(numberArg, self._tmpTxt), stacklevel=0)
                # resetwarnings()  # no noticeable effect on the code
                sys.stderr.flush() # this provides warning ahead of the output instead of after it

            print("loop count %d:" %(i))    
            print("If this were real code, it would be doing more stuff here ...")

mc1 = myClass(1001)
print("====================")
mc2 = myClass(20001)

稍后在github上寻找这段代码。我在此发布它是为了帮助其他人调查如何使用warnings


1
你仍然需要导入 sys,因为模块中的导入不会被导入者的命名空间导入。(你可以使用 warnings.sys,但是根据文档,这个方法并不能保证可行。) - jirassimok

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接