什么是Pythonic在解析器中报告非致命错误的方法?

时间:2014-10-14 09:35:19

标签: python exception-handling error-handling warnings

我创建的解析器从文件中读取记录的国际象棋游戏。 API的使用方式如下:

import chess.pgn

pgn_file = open("games.pgn")

first_game = chess.pgn.read_game(pgn_file)
second_game = chess.pgn.read_game(pgn_file)
# ...

有时会遇到非法移动(或其他问题)。什么是好的Pythonic方式来处理它们?

  • 遇到错误时立即引发异常。但是,这会使每个问题都致命,因为执行会停止。通常,仍然有用的数据已被解析并可以返回。此外,您不能简单地继续解析下一个数据集,因为我们仍然处于一些半读数据的中间。

  • 在游戏结束时累积异常并提升它们。这使得错误再次致命,但至少你可以抓住它并继续解析下一场比赛。

  • 引入一个像这样的可选参数:

    game = chess.pgn.read_game(pgn_file, parser_info)
    if parser_info.error:
       # This appears to be quite verbose.
       # Now you can at least make the best of the sucessfully parsed parts.
       # ...
    

这些或其他方法是否在野外使用?

5 个答案:

答案 0 :(得分:9)

最Pythonic方式是logging模块。在评论中已经提到过,但遗憾的是没有足够强调这一点。 warnings优先考虑的原因有很多:

  1. 警告模块旨在报告有关潜在代码问题的警告,而不是错误的用户数据。
  2. 第一个原因实际上已经足够了。 : - )
  3. 记录模块提供可调整的消息严重性:不仅可以报警,还可以报告从调试消息到严重错误的任何内容。
  4. 您可以完全控制记录模块的输出。消息可以按其来源,内容和严重性进行过滤,以您希望的任何方式格式化,发送到不同的输出目标(控制台,管道,文件,内存等)......
  5. 记录模块将实际的错误/警告/消息报告和输出分开:您的代码可以生成适当类型的消息,而不必打扰它们如何呈现给最终用户。
  6. 日志记录模块是Python代码的事实标准。世界各地的每个人都在使用它。因此,如果您的代码正在使用它,将它与第三方代码(可能也使用日志记录)相结合将是轻而易举的。好吧,也许比微风更强大,但绝对不是5级飓风。 : - )
  7. 日志记录模块的基本用例如下:

    import logging
    logger = logging.getLogger(__name__) # module-level logger
    
    # (tons of code)
    logger.warning('illegal move: %s in file %s', move, file_name)
    # (more tons of code)
    

    这将打印如下消息:

    WARNING:chess_parser:illegal move: a2-b7 in file parties.pgn
    

    (假设您的模块名为chess_parser.py)

    最重要的是您不需要在解析器模块中执行任何其他操作。您声明您正在使用日志系统,您正在使用具有特定名称的记录器(与此示例中的解析器模块名称相同),并且您正在向其发送警告级别消息。您的模块不必知道如何处理,格式化这些消息并将其报告给用户。或者如果他们被报道的话。例如,您可以配置日志记录模块(通常在程序的最开头)使用不同的格式并将其转储到文件中:

    logging.basicConfig(filename = 'parser.log', format = '%(name)s [%(levelname)s] %(message)s')
    

    突然,在没有对模块代码进行任何更改的情况下,您的警告消息将保存到具有不同格式的文件中,而不是打印到屏幕上:

    chess_parser [WARNING] illegal move: a2-b7 in file parties.pgn
    

    如果您愿意,也可以禁止警告:

    logging.basicConfig(level = logging.ERROR)
    

    您的模块警告将被完全忽略,而模块中的任何ERROR或更高级别的消息仍将被处理。

答案 1 :(得分:6)

实际上,那些致命错误 - 至少,只要能够重现正确的游戏;另一方面,也许玩家确实做了非法移动,当时没有人注意到(这会使其成为警告,而不是致命的错误)。

鉴于可能存在致命错误(文件已损坏)和警告(非法移动,但随后的移动显示与此移动的一致性(换句话说,用户错误,当时没有人抓住它))我建议第一个和第二个选项的组合:

  • 在继续解析时不提出异常
  • 收集任何不排除进一步解析的错误/警告

如果您没有遇到致命错误,那么您可以在最后返回游戏,加上任何警告/非致命错误:

return game, warnings, errors

但是,如果你遇到致命错误怎么办?

没问题:创建一个自定义异常,您可以将游戏的可用部分和任何其他警告/非致命错误附加到:

raise ParsingError(
    'error explanation here',
    game=game,
    warnings=warnings,
    errors=errors,
    )

然后当你发现错误时,你可以访问游戏的可恢复部分,以及警告和错误。

自定义错误可能是:

class ParsingError(Exception):
    def __init__(self, msg, game, warnings, errors):
        super().__init__(msg)
        self.game = game
        self.warnings = warnings
        self.errors = errors

并在使用中:

try:
    first_game, warnings, errors = chess.pgn.read_game(pgn_file)
except chess.pgn.ParsingError as err:
    first_game = err.game
    warnings = err.warnings
    errors = err.errors
    # whatever else you want to do to handle the exception

这类似于subprocess模块处理错误的方式。

为了能够在游戏致命错误后检索和解析后续游戏,我建议您更改API:

  • 有一个游戏迭代器,它只返回每个游戏的原始数据(它只需要知道如何判断一个游戏何时结束,下一个游戏何时开始)
  • 让解析器获取原始游戏数据并对其进行解析(因此它不再负责您在文件中的位置)

这样一来,如果你有一个五个游戏的文件,两个游戏就死了,你仍然可以尝试解析游戏3,4和5。

答案 2 :(得分:3)

我提供了赏金,因为我想知道这是否真的是最好的方法。但是,我也在编写解析器,所以我需要这个功能,这就是我提出的。

warnings module正是您想要的。

一开始让我远离它的原因是文档中使用的每个示例警告都显示为these

Traceback (most recent call last):
  File "warnings_warn_raise.py", line 15, in <module>
    warnings.warn('This is a warning message')
UserWarning: This is a warning message

...这是不受欢迎的,因为我不希望它是UserWarning,我想要自己的自定义警告名称。

以下是解决方案:

import warnings
class AmbiguousStatementWarning(Warning):
    pass

def x():
    warnings.warn("unable to parse statement syntax",
                  AmbiguousStatementWarning, stacklevel=3)
    print("after warning")

def x_caller():
    x()

x_caller()

给出:

$ python3 warntest.py 
warntest.py:12: AmbiguousStatementWarning: unable to parse statement syntax
  x_caller()
after warning

答案 3 :(得分:3)

我不确定解决方案是否是pythonic,但我经常稍作修改使用它:解析器在生成器中完成它的工作并产生结果和状态代码。接收代码决定失败项目的内容:

def process_items(items)
    for item in items:
        try:
            #process item
            yield processed_item, None
        except StandardError, err:
            yield None, (SOME_ERROR_CODE, str(err), item)


for processed, err in process_items(items):
    if err:
       # process and log err, collect failed items, etc.
       continue
    # further process processed

更通用的方法是练习使用设计模式。 Observer的简化版本(当您注册特定错误的回调时)或Visitor(访问者有处理特定错误的方法,请参阅SAX解析器以获取见解)的简化版本可能是一个清晰且易于理解的解决方案。

答案 4 :(得分:0)

没有库,很难干净地完成这项工作,但仍然有可能。

根据具体情况,有不同的处理方法。

方法1:

将while循环的所有内容放在以下内容中:

while 1:
    try:
        #codecodecode
    except Exception as detail:
        print detail

方法2:

与方法1相同,除了具有多个try / except东西,因此它不会跳过太多代码&amp;你知道错误的确切位置。

抱歉,匆忙,希望这有帮助!