使用有用的消息来终止Python命令行脚本,pythonically

问题描述:

如果要处理的数据不完全正确,我正在编写的脚本应该退出到shell提示并带有有用的消息。用户应该修复被标记的问题,直到脚本开心并且不再以错误消息退出。我正在用TTD开发脚本,所以我在编写函数之前编写了一个pytest测试。使用有用的消息来终止Python命令行脚本,pythonically

most heavily up-voted answer here建议通过调用sys.exit或提高SystemExit来编辑脚本。

功能:

通过测试(其中_non-text.png是PNG文件,即,不以UTF-8编码):

def test_istext(): 
    with pytest.raises(SystemExit): 
     istext('_non-text.png') 

然而,脚本继续运行,并在try/except块执行后发表声明。

我希望脚本每次都完全退出,以便用户可以调试数据,直到它正确为止,脚本将完成它应该做的事(即处理一个包含UTF-8文本的目录文件,而不是PNG,JPG,PPTX ...文件)。

也试过:

下也抛出一个异常是一个子类的SystemExit通过上面的测试,但它也不会退出脚本:

def istext(file_to_test): 
    class NotUTF8Error(SystemExit): pass 
    try: 
     open(file_to_test).read(512) 
    except UnicodeDecodeError: 
     raise NotUTF8Error('File {} must be UTF-8.'.format(file_to_test)) 

您可以使用raise Exception from exception语法:

class MyException(SystemExit): 
    pass 


def istext(file_to_test): 
    try: 
     open(file_to_test).read(512) 
    except UnicodeDecodeError as exception: 
     raise MyException(f'File {file_to_test} must be encoded in UTF-8 (Unicode); try converting.') \ 
      from exception 

我这种情况下,你不改变原来的错误信息,并添加您自己的消息。

+0

嗯,我无法得到这个例子工作,即使没有错误信息。 –

+0

声明类'NotUTF8Error(SystemExit):pass'后,'除UnicodeDecodeError作为异常:'后,从异常处引发NotUTF8Error将得到'SyntaxError'。 –

+0

没关系 - 我得到这个工作,只有pytest失败'NameError:name'NotUTF8Error'未定义。 –

的尝试... except块是为了捕获一个错误并在内部处理它。你想要做的是重新提高错误。

def istext(file_to_test): 
try: 
    open(file_to_test).read(512) 
except UnicodeDecodeError: 
    print(('File {} must be encoded in UTF-8 (Unicode); try converting.'.format(file_to_test))) 
    raise 

这将打印您的消息,然后自动重新提高您发现的错误。

而不是只重新提高旧的错误,您可能还想更改错误类型。对于这种情况,您可以指定进一步提高,例如:

raise NameError('I'm the shown error message') 
+0

我的第一本能已经创建沿线的自定义异常'类NotUTF8Error(SystemExit):'。所以你建议'除了UnicodeDecodeError之后:'我会'提出NotUTF8Error(“Some message”)'? –

+0

没错。或者你在错误类的定义中已经指定了文本的内容,这样就不需要重新提升。 – Sudix

+0

我尝试了第二个建议(请参阅上面的修改问题),但是像我最初的尝试,它通过pytest但不退出脚本。第一个建议(在打印消息之后重新提高'UnicodeDecodeError')会传递一个修改过的pytest,但不会退出脚本。 –

你问题不是如何退出程序(sys.exit()工作正常)。你的问题是你的测试场景没有提高UnicodeDecodeError

下面是您的示例的简化版本。它按预期工作:

import pytest 
import sys 

def foo(n): 
    try: 
     1/n 
    except ZeroDivisionError as e: 
     sys.exit('blah') 

def test_foo(): 
    # Assertion passes. 
    with pytest.raises(SystemExit): 
     foo(0) 
    # Assertion fails: "DID NOT RAISE <type 'exceptions.SystemExit'>" 
    with pytest.raises(SystemExit): 
     foo(9) 

将一些诊断打印添加到您的代码以了解更多信息。例如:

def istext(file_to_test): 
    try: 
     content = open(file_to_test).read(512) 
     # If you see this, no error occurred. Maybe your source 
     # file needs different content to trigger UnicodeDecodeError. 
     print('CONTENT len()', len(content)) 
    except UnicodeDecodeError: 
     sys.exit('blah') 
    except Exception as e: 
     # Maybe some other type of error should also be handled? 
     ... 
+0

这有助于澄清pytest没有测试'1/0'是否会引发'ZeroDivisionError',但是'sys.exit'是否会引发'SystemExit'。谢谢! –

到底,什么工作是类似于@ADR建议,有一点不同:我是不是能够得到如上图所示正确(f'File {file_to_test} must...')工作在格式化字符串语法,也不是我能查找字符串前缀f的文档。

我略少优雅的解决方案,那么,对于(改名)功能:

def is_utf8(file): 
    class NotUTF8Error(SystemExit): pass 
    try: 
     open(file).read(512) 
    except UnicodeDecodeError as e: 
     raise NotUTF8Error('File {} not UTF-8: convert or delete, then retry.'.format(file)) from e 

经过pytest:

def test_is_utf81(): 
    with pytest.raises(SystemExit): 
     is_utf8('/Users/tbaker/github/tombaker/mklists/mklists/_non-text.png') 
+0

你认真吗?您不再喜欢我的解决方案了,因为我使用了Python 3.6的一个简单功能? https://docs.python.org/3/whatsnew/3.6.html https://www.python.org/dev/peps/pep-0498/ – ADR

+0

@ADR感谢您的参考 - 我的道歉 - 我寻找该功能,但没有找到它;它不适合我,因为我有Python 3.5.2。我正在消除接受并接受你的答案! –