Python断言风格

4

我在想我所做的是否是合适的断言方法。我试图为Python的风格指南制作出既简洁又非常正确的东西。

try:
    assert self.port_number == 0
    assert self.handle == None
    assert isinstance(port_number, int) or isinstance(port_number, float)
    assert port_number > 0
except AssertionError:
    return -1

*body of code*

return 0

以下是我处理参数断言的代码片段,其中包含端口号信息。你可以默认我已经涵盖了所有必要的断言。这种写法是否算是良好的编程风格?是否有更好的方法?

如果这段代码是一个函数的主体,我会将其编辑为函数形式。 - Jonathon Reinhart
4个回答

6
assert语句只应用于检查程序的内部逻辑,不应用于检查用户输入或环境等方面的失败情况。引用自http://wiki.python.org/moin/UsingAssertionsEffectively的最后两段文字如下:

不应该使用断言来测试由于错误的用户输入或操作系统/环境故障(例如找不到文件)而导致的失败情况。相反,应该引发异常、打印错误消息或采取其他适当的措施。有一个重要的原因是,只有在进行程序的自我测试时,才应该使用断言,因为断言可以在编译时被禁用。

如果使用 -O 选项启动Python,则会剥离并且不评估断言。因此,如果代码大量使用断言但具有性能关键性,则存在一种系统可以在发布版本中将其关闭。(除非真正需要,否则不要这样做。已经有科学证明,有些错误只有客户使用机器时才会出现,我们也希望断言在这方面提供帮助。)

考虑到这一点,在用户代码中几乎没有理由捕获断言,因为断言失败的整个目的是尽早通知程序员程序存在逻辑错误。

3

我更倾向于不在函数内部捕获断言(assertion)错误,而是确保调用者处理任何错误。这也允许调用者检查任何未处理的错误并检查回溯以确定出错原因。

您还可以在断言语句中添加错误消息。

assert x > 0, "x must be greater than 0"

2
如果调用函数期望成功时返回0,失败时返回-1,那么我会这样写:
def prepare_for_connection(*args, **kwargs):
    if (self.handle is not None):
        return -1
    if not (isinstance(port_number, int) or isinstance(port_number, float)): 
        return -1
    if port_number < 0:
        return -1

    # function body

    return 0

触发抛出和捕获断言错误机制来处理非异常情况的行为开销太大。断言更适用于语句应该总是为真的情况,但如果由于某些错误而不为真,则会在该位置大声生成错误或者最好在该位置处理它(使用默认值)。如果您愿意,可以将多个 if 条件合并为一个巨大的条件语句;就个人而言,我认为这样更易读。此外,Python 风格是使用 isis not 比较 None,而不是使用 ==!=
一旦程序离开调试阶段,Python 应该能够优化掉断言。请参见 http://wiki.python.org/moin/UsingAssertionsEffectively 尽管这种从函数返回错误号(-1 / 0)的 C 风格约定并不特别符合 Python 的风格,但我会用 False 替换 -1,用 True 替换 0 并给它起一个有语义的名称;例如,将其命名为 connection_prepared = prepare_for_connection(*args,**kwargs),那么 connection_prepared 就会是 TrueFalse,代码会非常易读。
connection_prepared = prepare_for_connection(*args,**kwargs)
if connection_prepared:
    do_something()
else:
    do_something_else()

1
return -1

Python处理错误的方法与C不同。如果提供的数据存在问题,只需让AssertionError通过,或引发一个带有自定义错误消息的TypeErrorValueError。使用assert语句和自定义错误消息是最简单的方法:

assert port_number > 0, "Invalid port number"

断言语句可以在编译时被禁用,这可能是重新考虑是否要在您的情况下使用断言语句的原因。通常做法是不使用断言语句来验证来自函数用户的输入,而仅用于内部检查。另一方面,理智检查和验证之间的界限并不明确。以下是没有断言语句的代码部分的示例:

if port_number <= 0:
    raise ValueError('Invalid port number')
if not isinstance(port_number, (int, float)):
    raise TypeError('Port number must be some kind of number')

个人而言,我使用断言语句来验证数据,如果无效的话,迟早会导致崩溃(参见“鸭子类型”)。在开发过程中,我也会大量使用断言语句来检查我的数据,就像在静态类型语言中一样。只有在我非常怀疑自己的代码的稳定性和可靠性时,才会使用这些类型的断言。

下一行:

assert self.handle == None

如果我没记错的话,PEP8建议您写成assert self.handle is None。至少这是得到比我更聪明的人认可的做法。
assert isinstance(port_number, int) or isinstance(port_number, float)

如果你真的需要这个,可以写成isinstance(port_number, (int, float))。但实际上你不需要这样做。你不应该关心是否有人传递了一个数字原始类型或一些重载了所有比较运算符的自定义类。
也许你可以尝试将端口转换为整数,然后看看它是否可用:
try:
    port_number = int(port_number)
except ValueError:
    raise ValueError("Invalid port number")

在这种情况下,您也可以让 ValueError 通过,但对于新手来说,信息可能不够清晰。


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