为什么我们需要 Python 中的 try/except 的 except 块?

Why do we need an except block for the try/except in Python?

我明白它的作用,但我不明白我们为什么要这样做?为什么我们要排除某些东西?

例如:

    for i, value in enumerate(arr):
        try:
            result_dict[value] += 1
        except KeyError:
            result_dict[value] = 1

除了 KeyError 为什么我必须做?我不知道为什么会抛出 KeyError。为什么我不能做

result_dict[value] += 1

第一?

任何 try/catch 的 except 块中包含什么?我知道必须抛出错误,但在 except 块内必须执行什么条件?

抱歉,如果我的问题太愚蠢了。我是初学者。

原因如下。您不能添加 None 的内容。例如说 var1 = None。我不能做类似 var = var + 1 的事情。那会引发错误。在您共享的示例中,代码将检查您是否可以将 1 添加到值的键。如果您不能这样做,您可以为该键分配一个值。

for i, value in enumerate(arr):
        try:
            #Try and add 1 to a dict key's value. 
            result_dict[value] += 1
        except KeyError:
            #Say we get an error we need to assign the dict key's value to 1.
            result_dict[value] = 1
如果 result_dict[value] 不存在,

A KeyError 将 crash/terminate 您的程序。通过使用 except 块,您可以告诉程序在出现 KeyError 时要做什么(这里,如果它不存在,则分配 result_dict[value] = 1),然后程序继续。基本上,您是在避免崩溃。

假设您的 value"banana"。如果你没有 result_dict["banana"],你不能加 1 到无,即 None += 1.

通过使用 except KeyError,您可以在程序停止之前拦截错误,现在您已经为 result_dict 设置了键值对,而不是终止程序。

TLDR

try/except 块确保您的程序继续 运行ning 而不是突然结束。通过包含 except KeyError,我们特别确保程序不会在出现 KeyError 时突然结束。如果 value 不是字典中的键,result_dict[value] += 1 将抛出错误,因为它试图访问不存在的键。 += 使代码 运行 类似于:

result_dict[value] = result_dict[value] + 1

并且由于 value 不在 result_dict 中,这类似于说

result_dict[value] = None + 1

哪个不好。

非 TLDR 版本

当python发生错误时,程序通常会突然终止并退出。它不会 运行 发生异常的部分下方出现的任何代码。例如,采用以下代码。它从用户 ab 那里获取 2 个数字,它会输出 a/b

a = int(input("Enter a value for a: "))
b = int(input("Enter a value for b: "))

print("a/b is:", a/b)

如果用户提供有效输入(比如 a=4, b=2),程序将顺利进行。但是,如果用户要给出 a = "c",则会发生以下情况

Traceback (most recent call last):
  File "<string>", line 1, in <module>
ValueError: invalid literal for int() with base 10: 'c'

然后程序戛然而止。让程序像这样结束是完全正确的,但你很少希望程序突然结束。假设用户有 1000 个输入,最后一个输入搞砸了。然后用户将不得不重新启动程序并再次重新输入所有 1000 个输入,因为程序突然结束。

所以现在我们引入一个try/except块。我们知道将非数字字符转换为整数会抛出一个 ValueError ,如错误所示,因此我们将相应地处理它们

while True:
    try:
        a = int(input("Enter a value for a: "))
        break
    except:
        print("An error has occurred. Please input a again")

while True:
    try:
        b = int(input("Enter a value for b: "))
        break
    except:
        print("An error has occurred. Please input b again")

print("a/b is:", a/b)

所以现在,我们从用户那里获取输入,尝试 将其转换为整数并将该值放入 a。如果成功,它将顺利进行到下一行(break)并退出循环。如果失败,则会出现 exception,然后进入 except 块,打印有用的错误消息,然后再次 运行 循环(直到用户输入有效输入)。现在程序不会在失败时突然终止。它向用户提供适当的反馈,并让用户再次尝试输入。

这是一个通用的异常块,您可以在其中捕获任何异常。

但是现在假设可能会发生多种可能的错误。取以下代码:

a = input()
b = input()

print(int(a)/int(b))
print("I am done")

现在可能会出现一些错误。其中之一就是上面提到的ValueError,其中给定的输入无法转换为整数。另一个错误是 ZeroDivisionError,其中 b=0。 Python 不喜欢除以零,因此会抛出异常并立即终止程序。

现在,您想要为每种类型的程序打印一条特殊消息。你如何做到这一点是通过 捕获特定的异常

a = input("Enter a value for a: ")
b = input("Enter a value for b: ")

try:
    print(int(a)/int(b))
except ValueError:
    print("We cannot convert a non-numeric character to an integer")
except ZeroDivisionError:
    print("We cannot divide by 0")
except:
    print("Some unexpected error has occurred")

print("I am done")

如果python无法将输入转换为整数,则会进入代码的ValueError块,打印"We cannot convert a non-numeric character to an integer"

如果python试图除以0,那么它将进入ZeroDivisionError块并打印"We cannot divide by 0"

如果发生任何其他类型的错误,它将在最后的 except 块中结束并打印 "Some unexpected error has occurred"

并且在处理异常后,打印"I am done"

请注意,如果最后的 except 块没有捕获任何其他异常,那么程序将不会很好地终止,因为异常未被处理,并且它不会打印最终语句。

现在,如何意识到可能发生的错误?这要靠实践,一旦你熟悉了错误,你就可以相应地处理它们。或者,故意让您的程序抛出异常,并读取堆栈跟踪以了解发生了何种异常。然后相应地处理它们。您不必以不同方式处理每个特定错误,您可以用相同的方式处理所有错误。

您可以在此处阅读更多内容:Python's documentation for errors and exceptions

让我们从一个例子开始

我们的公寓楼里有租户,他们可以预订停车位。有些租户没有车,也没有停车位,但只有租户可以预订停车位。我们在字典中跟踪停车位:

parking_spots = { "alice": 1, "bob": 2, "chris": 0 }

克里斯没有斑点,因为他步行上班。

如果 Eve 试图保留位置会怎样?

parking_spots["eve"]

该代码询问“已经保留了多少个位置?”然而,这个回答的另一个问题是夏娃是否是大楼的租户。 Python 通过让 parking_spots["eve"] 抛出一个不同于任何其他值的 KeyError 来表示这一点。如果 python 没有这样做并且默认情况下 returned 0,那么 parking_spots["eve"]parking_spots["chris"] 看起来是一样的。

但是等等,我知道这本特定的词典不是那样使用的

太棒了,这很常见。事实上,这很常见,有多种方法可以做到这一点。

而不是

result_dict = {}
for i, value in enumerate(arr):
    try:
        result_dict[value] += 1
    except KeyError:
        result_dict[value] = 1

你可以使用

result_dict = {}
result_dict.setdefault(0)

result_dict += 1

或者您可以使用 defaultdict.

from collections import defaultdict

result_dict = defaultdict(int)
result_dict += 1

一些CS理论

我们可以在这里讨论两个理论概念:

  • 作为控制流机制的异常
  • 字典api

控制流异常

try/catch 是控制流的样式。它只是一种更好的方式来考虑某些情况下的代码。只要您可以使用 if/else,几乎从不 需要 ,但很多时候这是思考代码的最佳方式。

根据您与谁交谈,异常处理可能是一个有争议的功能: C++ - Arguments for Exceptions over Return Codes

在我看来,如果您从头开始设计系统,异常很少是真正正确的答案。通常,我更喜欢使用 Option(或更普遍的求和类型)。

但是,在很多情况下,它们是给定起始限制的最佳答案。

词典API

Python 在键不存在时抛出异常是一种设计选择,而不是 API 可以编写的唯一方式。相反,它可以简单地 return None。许多语言都采用后一种方法,因为他们认为它更安全——尤其是当您使用 Rust、Haskell、Elm 等现代类型语言时

进一步阅读

我还鼓励您阅读 ,因为它涵盖了一些可能具有指导意义的异常处理细节的其他细节。