raise、try 和 assert 之间有什么区别?

What's the difference between raise, try, and assert?

我已经学习 Python 一段时间了,raise 函数和 assert 是(我意识到它们都使应用程序崩溃,不像 try - except)真的很相似,我看不到在 try.

上使用 raiseassert 的情况

那么,raisetryassert有什么区别呢?

statement assert 可用于在运行时检查条件,但如果从 Python 请求优化, 将被删除。扩展形式为:

assert condition, message

相当于:

if __debug__:
    if not condition:
        raise AssertionError(message)

其中 __debug__True 是 Python 是 而不是 以选项 -O.

开始

所以语句 assert condition, message 类似于:

if not condition:
    raise AssertionError(message)

因为两者都提高了 AssertionError。不同之处在于 assert condition, message 可以通过优化从执行的字节码中删除 (当启用这些时——默认情况下它们不会应用于 CPython)。相反,raise AssertionError(message) 将在所有情况下执行。

因此,如果代码在任何情况下都应该检查并在检查失败时引发 AssertionError,那么写 if not condition: raise AssertionError 是必要的。

raise - 引发异常。

assert - 引发异常 if 给定条件为(或不为)真。

try - 执行一些 可能 引发异常的代码,如果是,则捕获它。

异常是Python(和其他一些语言)用来处理执行代码时出现的错误的东西。 raise ExceptionName 表示代码中存在错误,并通过引发与该问题关联的异常来指定问题类型。 assert expression 评估 expression 并在为假时引发异常。

try 用于执行可能引发您期望的异常的代码。您可以 "catch" 异常并在您的代码中处理它,而不是停止程序。

示例:假设您有一本字典和一个列表。您想从字典中的列表中查找内容,直到找到不在字典中的内容:

try:
    for item in my_list:
        print(my_dictionary[item])
except KeyError as e: #KeyError is the Exception raised when a key is not in a dictionary
    print('There is no {} in the dictionary'.format(e.args[0]))

try/except 块可让您捕获和管理异常。 raiseassert 以及尝试索引空列表等大量错误都可能触发异常。 raise 通常在检测到错误情况时使用。 assert 类似,但只有在满足条件时才会引发异常。

raiseassert 有不同的理念。您检测到并引发错误的代码中有许多 "normal" 错误。也许网站不存在或参数值超出范围。

断言通常用于 "I swear this cannot happen" 似乎无论如何都会发生的问题。它更像是 运行 时间调试而不是正常的 运行 时间错误检测。如果您使用 -O 标志或 .pyo 文件中的 运行 而不是 .pyc 文件,则可以禁用断言,因此它们不应成为常规错误检测的一部分。

如果生产质量代码引发异常,请找出您做错了什么。如果它引发 AssertionError,你的问题就大了。

断言:

当你想根据特定条件“停止”脚本时使用 return 有助于更快调试的东西:

list_ = ["a","b","x"]
assert "x" in list_, "x is not in the list"
print("passed") 
#>> prints passed

list_ = ["a","b","c"]
assert "x" in list_, "x is not in the list"
print("passed")
#>> 
Traceback (most recent call last):
  File "python", line 2, in <module>
AssertionError: x is not in the list

提高:

这两个有用的原因:

1/ 与 try 和 except 块一起使用。引发您选择的错误,可以像下面这样自定义,并且如果您 passcontinue 脚本不会停止脚本;或者可以是预定义的错误 raise ValueError()

class Custom_error(BaseException):
    pass

try:
    print("hello")
    raise Custom_error
    print("world")
except Custom_error:
    print("found it not stopping now")

print("im outside")

>> hello
>> found it not stopping now
>> im outside

注意到它没有停止吗?我们可以在 except 块中使用 exit(1) 来停止它。

2/ Raise 也可用于重新引发当前错误以将其向上传递到堆栈以查看是否有其他方法可以处理它。

except SomeError, e:
     if not can_handle(e):
          raise
     someone_take_care_of_it(e)

Try/Except 块:

完全按照您的想法行事,如果出现错误,您会尝试一些事情,您会发现它并按照您喜欢的方式处理它。上面有例子就不举例了

测试代码通常使用 Assert 来确保某些东西有效:

def test_bool():
    assert True != False

Where as try, raise and except makeup 异常处理是 python 处理和传播错误的首选方式。

如果出现问题,大多数库和 python 内置函数将引发一种或另一种类型的异常。通常在您自己的代码中,您还希望在检测到出现问题时引发异常。举个例子,你正在编写一个电子邮件地址验证器,如果地址不包含 @ 符号,你想抛出一个异常。你可以有类似的东西(这是玩具代码,实际上不要像这样验证电子邮件):

def validate_email(address):
    if not "@" in address:
        raise ValueError("Email Addresses must contain @ sign")

然后您可以在代码的其他地方调用 validate_email 函数,如果失败,将抛出异常。

try:
    validate_email("Mynameisjoe.com")
except ValueError as ex:
    print("We can do some special invalid input handling here, Like ask the user to retry the input")
finally:
    close_my_connection()
    print("Finally always runs whether we succeed or not. Good for clean up like shutting things down.")

要知道的重要一点是,当引发异常时,它会向上传递调用堆栈,直到找到处理程序。如果它从未找到处理程序,那么它将使程序崩溃并出现异常和堆栈跟踪。

你不想做的一件事是:

if __name__ == '__main__':
    try:
        print(1/0)
    except Exception as ex:
        pass

现在你无法知道为什么你的应用程序崩溃了。

你会经常看到的一件事是:

import logging
if __name__ == '__main__':
    try:
        print(1/0)
    except Exception as ex:
        logging.exception(ex)
        raise

本例中的 raise 因为它没有参数而重新引发相同的错误。通常在 Web 代码中你会看到类似的东西,它不会重新引发异常,因为它会向客户端发送 500 错误,然后继续下一个请求,所以在那种情况下你不希望程序结束。

断言

  • 应该只用于调试目的
  • 虽然与 Raise/Exceptions 相似,但它们的用途不同,因为它们有助于指出无法从
  • 中恢复程序错误的场景
  • 断言总是引发 AssertionError 异常,这是它们的工作原理:

语法assert_stmt ::= "assert" expression1 ["," expression2]

在执行时转换为:

if __debug__:
  if not expression1:
    raise AssertionError(expression2)
  • __debug__ 是一个内置标志,通常为 true,但如果触发优化,它将为 false,因此断言将是死代码 => 在启动时使用 -O 和 -OO 标志禁用Python(或 CPython 中的 PYTHONOPTIMIZE env 变量),因此,代码逻辑不要依赖它们。
  • 由于前一点,不要使用断言进行数据验证
  • 断言的一个很好的用例 => 编写程序 "explode" 如果程序的某些意外状态应该使其在任何情况下都停止 => 因此,在捕获到异常会使程序完全退出的情况下.
  • 如果您有一个没有错误的程序,那么断言 will/should 永远不会被触发,它们用作程序的健康检查
  • 在断言中使用数据结构(例如元组)作为 expression1 时要小心,对于非空值始终求值为 True => 断言将始终被触发,从而破坏程序 - 例如:assert (<some_test>, 'warn string') => 注意元组结构(错误!)

检查:Catching bogus Python asserts on CI by Dan Bader

Raise/Exceptions

  • 它们的目的是处理程序逻辑处于异常状态但您知道从该状态恢复什么逻辑的情况
  • 当您引发异常时,您可以使异常类型适合于错误(更好地控制语义值)并稍后捕获它 => 这样您就可以创建多个您知道如何从中恢复的异常类型, 并处理它们
  • 它们是一种处理 known/expected 运行 时间错误场景的机制
  • 在使用 if 语句和针对每个场景引发验证异常时对数据验证很有用

试试

  • 只是编码异常处理的句法元素

顺便说一句,我强烈推荐这本书,"Python Tricks: The Book" by Dan Bader (from realpython.com)

当它们就位时,assertraise AssertionError 之间没有区别,它们将编译为完全相同的字节码:

import dis

def foo1(param):
    assert param, "fail"

def foo2(param):
    if not param:
        raise AssertionError("fail")

dis.dis(foo1)
print("*" * 60)
dis.dis(foo2)

Output:

 4           0 LOAD_FAST                0 (param)
             2 POP_JUMP_IF_TRUE        12
             4 LOAD_GLOBAL              0 (AssertionError)
             6 LOAD_CONST               1 ('fail')
             8 CALL_FUNCTION            1
            10 RAISE_VARARGS            1
       >>   12 LOAD_CONST               0 (None)
            14 RETURN_VALUE
************************************************************
 7           0 LOAD_FAST                0 (param)
             2 POP_JUMP_IF_TRUE        12

 8           4 LOAD_GLOBAL              0 (AssertionError)
             6 LOAD_CONST               1 ('fail')
             8 CALL_FUNCTION            1
            10 RAISE_VARARGS            1
       >>   12 LOAD_CONST               0 (None)
            14 RETURN_VALUE

但请记住,当 运行 Python 带有 -O-OO 标志时,assert stataments 将被禁用,但事实并非如此带有任何 raise 语句。

其他 很好地解释了差异,但许多人没有提到使用 -O 优化器标志时会忽略 assert 语句。

获得与 assert 类似的简洁语法的一个选项,在使用 -O 时仍然使异常生效,并获得能够引发特定异常类型的好处是定义实用程序函数像这样:

def raiseif(cond, msg="", exc=AssertionError):
    if cond:
        raise exc(msg)

raiseif(x != y, "x should equal y")

逻辑与 assert 相反,但您可以根据需要轻松更改。

  • raise用于引发异常;
  • assert 用于在给定条件为 False.
  • 时引发异常