【问题标题】:Why do we need an except block for the try/except in Python?为什么 Python 中的 try/except 需要一个 except 块?
【发布时间】:2021-07-14 23:26:55
【问题描述】:

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

例如:

    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 块内必须做什么?

对不起,如果我的问题太愚蠢了。我是初学者。

【问题讨论】:

    标签: python try-catch except


    【解决方案1】:

    TLDR

    try/except 块确保您的程序继续运行而不是突然结束。通过包含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)并退出循环。如果失败,则会发生异常,它会进入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

    【讨论】:

      【解决方案2】:

      原因如下。你不能添加到无的东西。例如说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
      

      【讨论】:

        【解决方案3】:

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

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

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

        【讨论】:

          【解决方案4】:

          让我们从一个例子开始

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

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

          克里斯走路上班,所以没有位置。

          如果 Eve 试图预订位置,会发生什么?

          parking_spots["eve"]
          

          该代码询问“前夕保留了多少个位置?”然而,这个回答的另一个问题是 Eve 是否是该建筑物的租户。 Python 通过让parking_spots["eve"] 抛出与任何其他值不同的KeyError 来表示这一点。如果python没有这样做并且默认返回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 的唯一方式。相反,它可以简单地返回None。许多语言遵循后一种方法,因为他们认为它更安全——尤其是当您使用现代类型语言(如 Rust、Haskell、Elm 等)时。

          进一步阅读

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2019-06-26
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多