Heim  >  Artikel  >  Backend-Entwicklung  >  So lösen Sie Python-Fehlerausnahmen

So lösen Sie Python-Fehlerausnahmen

WBOY
WBOYnach vorne
2023-05-16 16:37:132049Durchsuche

Beginnen Sie mit dem Fangen

Falls Sie mit Ausnahmen nicht vertraut sind, beginnen wir mit einer allgemeinen Definition ...

Ausnahme: (Rechnerisch) Eine Unterbrechung, die während der normalen Verarbeitung auftritt und normalerweise durch eine Fehlerbedingung verursacht wird. und kann durch einen anderen Teil des Prozesses verursacht werden.

Schauen wir uns ein einfaches Beispiel an:

def initiate_security_protocol(code):
    if code == 1:
        print("Returning onboard companion to home location...")
    if code == 712:
        print("Dematerializing to preset location...")

code = int(input("Enter security protocol code: "))
initiate_security_protocol(code)
>>> Enter security protocol code: 712
Dematerializing to preset location...
>>> Enter security protocol code: seven one two
Traceback (most recent call last):
  File "/home/jason/Code/FiringRanges/PyFiringRange/Sandbox/security_protocols.py", line 7, in <module>
    code = int(input("Enter security protocol code: "))
ValueError: invalid literal for int() with base 10: 'seven one two'

Das ist offensichtlich ein Fehler. Wir möchten nicht, dass unser Programm plötzlich abstürzt, weil der Benutzer etwas Seltsames eingegeben hat. Wie dieser Witz lautet...

Ein QA-Ingenieur betritt eine Bar. Er bestellte ein Bier. Er bestellte fünf Bier. Er bestellte -1 Bier. Er bestellte eine Eidechse.

Wir wollen seltsame Eingaben verhindern. In diesem Fall gibt es nur einen wichtigen Fehlerpunkt: die Funktion int(). Es erwartet ein Argument, das in eine Ganzzahl konvertiert werden kann. Wenn es dieses nicht erhält, wird eine ValueError-Ausnahme ausgelöst. Um dies richtig zu handhaben, verpacken wir Code, der möglicherweise fehlschlägt, in einen try...exclusive-Block. int()函数。它期望接收可以转换为整数的参数,如果它没有得到它,则会抛出ValueError异常。为了正确处理这个问题,我们将可能失败的代码包装在一个try...except块中。

try:
    code = int(input("Enter security protocol code: "))
except ValueError:
    code = 0
initiate_security_protocol(code)

当我们再次测试我们的代码时,我们不会遇到这种失败错误。如果我们无法从用户那里获得我们需要的信息,我们将只需要设置code=0。当然,我们可以重写我们的initiate_security_protocol()函数来处理0不同的代码,但是我不会在这里展示,只是为了节省时间。

注意:无论出于何种原因,作为一名多语言程序员,我经常忘记在 Python 中使用except,而用大多数其他语言所使用的catch语句。我已经在这篇文章中打错了三遍(然后立即修复它)。这只是一个记忆点。值得庆幸的是,Python没有catch的关键字,因此语法错误会很突出。如果你会多种语言,当你感到困惑时,请不要惊慌。python里是except,不是catch

阅读Traceback

在我们深入探讨该try...except语句的一些更深层次的细节之前,让我们再次回顾一下该错误语句。毕竟,如果我们不讨论错误消息,那么一篇关于错误处理的文章有什么用呢?在 Python 中,我们称之为Traceback,因为它从涉及的第一行代码到最后一行跟踪错误的起源。在许多其他语言中,这将被称为堆栈跟踪( stack trace)

Traceback (most recent call last):
  File "/home/jason/Code/FiringRanges/PyFiringRange/Sandbox/security_protocols.py", line 7, in <module>
    code = int(input("Enter security protocol code: "))
ValueError: invalid literal for int() with base 10: 'seven one two'

我有从下到上阅读这些信息的习惯,因为它可以帮助我首先获得最重要的信息。如果你查看最后一行,你会看到ValueError,这是已引发的特定异常。确切的细节如下;在这种情况下,无法使用 . 将字符串'seven one two'int()转换为整数。我们还了解到它正在尝试转换为以10 为底的整数,这在其他场景中可能是有用的信息。想象一下,例如,如果那行改成...

ValueError: invalid literal for int() with base 10: '5bff'

如果我们忘记指定以 16 为基数进行int('5bff', 16)转化,而不是默认值(以 10 为基数),这是完全可能的。简而言之,你应该始终彻底阅读并理解错误消息的最后一行!有太多次我看了一半的帖子,花了半个小时追错了bug,才发现我忘记了一个参数或使用了错误的函数。

错误消息上方是错误来自 ( code = int(input("Enter security protocol code: "))) 的代码行。上面是文件的绝对路径 ( security_protocols.py) 和行号7。该语句in <module>意味着代码在任何函数之外。在这个例子中,回调只有一步,所以让我们看一些稍微复杂一点的东西。我已经更改并扩展了之前的代码。

Traceback (most recent call last):
  File "/home/jason/Code/FiringRanges/PyFiringRange/Sandbox/databank.py", line 6, in <module>
    decode_message("Bad Wolf")
  File "/home/jason/Code/FiringRanges/PyFiringRange/Sandbox/databank.py", line 4, in decode_message
    initiate_security_protocol(message)
  File "/home/jason/Code/FiringRanges/PyFiringRange/Sandbox/security_protocols.py", line 2, in initiate_security_protocol
    code = int(code)
ValueError: invalid literal for int() with base 10: 'Bad Wolf'

我们遇到了与以前类似的错误 - 我们正在尝试将字符串转换为整数,但它不起作用。倒数第二行向我们展示了失败的代码;果然,这儿提到了int()这一点。根据上面的行,这个有问题的代码位于security_protocols.py文件的第2 行initiate_security_protocol()函数内部!我们就可以马上找到那儿,并将其包装在一个try...except

datafile_index = {
    # Omitted for brevity.
    # Just assume there's a lot of data in here.
}

def get_datafile_id(subject):
    id = datafile_index[subject]
    print(f"See datafile {id}.")

get_datafile_id("Clara Oswald")
get_datafile_id("Ashildir")

Wenn wir unseren Code erneut testen, wird dieser Fehler nicht auftreten. Wenn wir vom Benutzer nicht die benötigten Informationen erhalten können, setzen wir einfach code=0. Natürlich könnten wir unsere Funktion initiate_security_protocol() umschreiben, um 0 unterschiedlichen Code zu verarbeiten, aber ich werde ihn hier nicht zeigen, nur um Zeit zu sparen.

🎜Hinweis: 🎜Aus irgendeinem Grund vergesse ich als polyglotter Programmierer oft, exclus in Python zu verwenden und verwende stattdessen The catch-Anweisung, die von den meisten anderen Sprachen verwendet wird. Ich habe es in diesem Beitrag dreimal falsch eingegeben (und es dann sofort behoben). Dies ist nur ein Erinnerungspunkt. Glücklicherweise erkennt Python das Schlüsselwort nicht, sodass Syntaxfehler auffallen. Wenn Sie mehrsprachig sind, geraten Sie nicht in Panik, wenn Sie verwirrt werden. In Python ist es <code>außer, nicht catch. 🎜

Lesen Sie den Traceback🎜

, bevor wir uns mit diesem versuchen...außerbefassen > Bevor wir näher auf die Aussage eingehen, schauen wir uns noch einmal die Fehleraussage an. Denn was nützt ein Artikel zur Fehlerbehandlung, wenn wir nicht auf Fehlermeldungen eingehen? In Python nennen wir es „Traceback“, weil es den Ursprung eines Fehlers von der ersten bis zur letzten Codezeile verfolgt. In vielen anderen Sprachen würde man dies als Stack-Trace bezeichnen. 🎜

See datafile 6035215751266852927.

Traceback (most recent call last):
  File "/home/jason/Code/FiringRanges/PyFiringRange/Sandbox/databank.py", line 30, in <module>
    get_datafile_id("Ashildir")
  File "/home/jason/Code/FiringRanges/PyFiringRange/Sandbox/databank.py", line 26, in get_datafile_id
    id = datafile_index[subject]
KeyError: 'Ashildir'

Ich habe die Angewohnheit, diese Informationen von unten nach oben zu lesen, weil es mir hilft, die wichtigsten Informationen zuerst zu bekommen. Wenn Sie sich die letzte Zeile ansehen, sehen Sie ValueError, die spezifische Ausnahme, die ausgelöst wurde. Die genauen Details lauten wie folgt: In diesem Fall können Sie die Zeichenfolge 'seven one two' nicht mit int() umwandeln. Wir erfahren auch, dass versucht wird, in eine Ganzzahl zur Basis 10 zu konvertieren, was in anderen Szenarien nützliche Informationen sein kann. Stellen Sie sich zum Beispiel vor, diese Zeile würde geändert in... 🎜

def get_datafile_id(subject):
    if subject in datafile_index:
        id = datafile_index[subject]
        print(f"See datafile {id}.")
    else:
        print(f"Datafile not found on {subject})

Was wäre, wenn wir vergessen hätten, die Basis 16 für int('5bff', 16) anzugeben? code >Convert anstelle der Standardeinstellung (Basis 10), was durchaus möglich ist. Kurz gesagt: 🎜Sie sollten die letzte Zeile einer Fehlermeldung immer gründlich lesen und verstehen! 🎜Es kam zu oft vor, dass ich einen halben Beitrag gelesen und eine halbe Stunde damit verbracht habe, einem Fehler nachzujagen, nur um dann festzustellen, dass ich einen Parameter vergessen oder die falsche Funktion verwendet habe. 🎜<p data-id="p838747a-AIWJqVPB">Über der Fehlermeldung befindet sich die Codezeile, aus der der Fehler stammt ( <code>code = int(input("Geben Sie den Sicherheitsprotokollcode ein: ")) ). Oben ist der absolute Pfad zur Datei ( security_protocols.py) und die Zeilennummer 7. Die Anweisung in <module> bedeutet, dass der Code außerhalb einer Funktion liegt. In diesem Beispiel ist der Rückruf nur ein Schritt, also schauen wir uns etwas etwas Komplexeres an. Ich habe den vorherigen Code geändert und erweitert. 🎜

def get_datafile_id(subject):
    try:
        id = datafile_index[subject]
        print(f"See datafile {id}.")
    except KeyError:
        print(f"Datafile not found on {subject}")

Wir sind auf einen ähnlichen Fehler wie zuvor gestoßen – wir haben versucht, eine Zeichenfolge in eine Ganzzahl umzuwandeln, aber es hat nicht funktioniert. Die vorletzte Zeile zeigt uns den fehlerhaften Code; tatsächlich gibt es einen Verweis auf int(). Der obigen Zeile zufolge befindet sich der problematische Code in der Funktion initiate_security_protocol() in Zeile 2 der Datei security_protocols.py! Wir können es sofort finden und in einen try...außer verpacken. Verstehen Sie, warum das Lesen von unten nach oben Zeit spart? 🎜

然而,让我们想象它并不那么简单。也许我们没有修改security_protocols.py的选项,所以我们需要在执行该模块之前防止出现问题。如果我们查看下一行,我们会看到在databank.py第 4 行,在decode_message()函数内部,我们调用的initiate_security_protocol()函数有问题。是由于在databank.py第6行被调用,这就是我们将参数传递"Bad Wolf"给它的地方。

数据输入不是问题,因为我们要解码消息“Bad Wolf”。但是,为什么我们要将我们试图解码的消息传递给安全协议呢?也许我们需要改写那个函数(或者除了其他更改之外?)。如你所见,Traceback对于了解错误的来源非常重要。养成仔细阅读的习惯;许多有用的信息可能隐藏在意想不到的地方。

顺便说一句,第一行每次都是一样的,但是如果你忘记如何阅读这些消息,它会非常有用。最近执行的代码列在最后。因此,正如我之前所说,你应该从下往上阅读它们。

Exception

“请求宽恕比获得许可更容易。” -海军少将格蕾丝·霍珀

这句话最初是关于主动的;如果你相信一个想法,请尽力去尝试它,而不是等待其他人的许可来追求它。然而,在这种情况下,它很好地描述了 Python 的错误处理哲学:如果某些事情经常以一种或多种特定方式失败,通常最好使用<strong>try...except</strong>语句来处理这些情况。

这种哲学被正式命名为“请求宽恕比许可更容易”,或EAFP

这有点抽象,所以让我们考虑另一个例子。假设我们希望能够在字典中查找信息。

datafile_index = {
    # Omitted for brevity.
    # Just assume there's a lot of data in here.
}

def get_datafile_id(subject):
    id = datafile_index[subject]
    print(f"See datafile {id}.")

get_datafile_id("Clara Oswald")
get_datafile_id("Ashildir")
See datafile 6035215751266852927.

Traceback (most recent call last):
  File "/home/jason/Code/FiringRanges/PyFiringRange/Sandbox/databank.py", line 30, in <module>
    get_datafile_id("Ashildir")
  File "/home/jason/Code/FiringRanges/PyFiringRange/Sandbox/databank.py", line 26, in get_datafile_id
    id = datafile_index[subject]
KeyError: 'Ashildir'

第一个函数调用是对的。我们在字典database_index中搜索存在的键"Clara Oswald",因此我们返回与其关联的值 (6035215751266852927 ),并在我们格式化语句print()中打印出该数据。但是,第二个函数调用失败。引发异常KeyError,因为"Ashildir"它不是字典中的键。

技术说明: Python 为collections.defaultdict这个确切问题提供了另一种解决方案;尝试访问不存在的键将使用一些默认值在字典中创建键/值对。但是,由于这是演示错误处理的示例,因此我没有使用它。

由于不能合理地期望我们知道或记住字典中的所有键,尤其是在现实世界的场景中,我们需要一些方法来处理尝试访问不存在的键的常见情况。你的第一直觉可能是在尝试访问字典键之前检查它......

def get_datafile_id(subject):
    if subject in datafile_index:
        id = datafile_index[subject]
        print(f"See datafile {id}.")
    else:
        print(f"Datafile not found on {subject})

在 Python 文化中,这种方法被称为“跳前看”[LBYL]。

但这不是最有效的方法!“宽恕,而不是许可”在这里发挥作用:我们不是先测试,而是使用<strong>try...except</strong>.

def get_datafile_id(subject):
    try:
        id = datafile_index[subject]
        print(f"See datafile {id}.")
    except KeyError:
        print(f"Datafile not found on {subject}")

这背后的逻辑很简单:我们不是通过两次获取键,而是只访问一次,并使用实际exception作为逻辑分支的手段。

在 Python 中,我们不认为异常是可以避免的。事实上,try...except它是许多 Python 设计模式和算法的常规部分。不要害怕引发和捕获异常!事实上,即使是键盘中断也是通过KeyboardInterrupt异常处理的。

注意: try...except是一个强大的工具,但它并不适用于一切。例如,None从函数返回通常被认为比引发异常更好。仅在发生最好由调用者处理的实际错误时抛出异常。

反面模式

迟早,每个 Python 开发人员都会发现这是可行的:

try:
    someScaryFunction()
except:
    print("An error occured. Moving on!")

一个单except语句允许你在一个中捕获所有异常。这个被称为反面模式,这是一个非常非常糟糕的想法。总结一下……

...实际错误的所有上下文放在了一起抛出,永远看不到问题跟踪器的内部。当发生“大量”异常时,堆栈跟踪指向发生次要错误的位置,而不是 try 块内的实际失败位置。

长话短说,你应该始终明确地捕获特定的异常类型。任何你无法预见的失败都可能与一些需要解决的错误有关;例如,当你的超级复杂搜索功能突然开始提出 anOSError而不是预期的KeyError或者TypeError时。

像往常一样,The Zen Python 对此有话要说……

错误永远不应该悄无声息地过去。
除非明确沉默。

换句话说,这不是口袋妖怪 - 你不应该抓住他们!

Except, Else, Finally

我不会一下子就捕捉到所有异常。那么,如何处理多个可能的故障呢?

你要知道 Python 的try...except工具比它最初展示的要多得多。

class SonicScrewdriver:

    def __init__(self):
        self.memory = 0

    def perform_division(self, lhs, rhs):
        try:
            result = float(lhs)/float(rhs)
        except ZeroDivisionError:
            print("Wibbly wobbly, timey wimey.")
            result = "Infinity"
        except (ValueError, UnicodeError):
            print("Oy! Don't diss the sonic!")
            result = "Cannot Calculate"
        else:
            self.memory = result
        finally:
            print(f"Calculation Result: {result}\n")


sonic = SonicScrewdriver()

sonic.perform_division(8, 4)
sonic.perform_division(4, 0)
sonic.perform_division(4, "zero")

print(f"Memory Is: {sonic.memory}")

在我向你展示输出之前,请仔细查看代码。你认为这三个sonic.perform_division()函数调用中的每一个都会打印出什么?最终存储sonic.memory的是什么?看看你能不能弄明白。

如果你已经有答案?让我们看看你是否正确。

Calculation Result: 2.0

Wibbly wobbly, timey wimey.
Calculation Result: Infinity

Oy! Don't diss the sonic!
Calculation Result: Cannot Calculate

Memory Is: 2.0

你是惊讶,还是你做对了?让我们分析一下。

try:当然,是我们试图运行的代码,它可能会也可能不会引发异常。

except ZeroDivisionError:当我们试图除以零时发生。在这种情况下,我们说该值"Infinity"是计算的结果,并打印出一条关于除以0的提示信息。

except (ValueError, UnicodeError):只要引发这两个异常之一,就会抛出异常。ValueError是每当我们传递的任何参数都不能被强制转换float()时,就会发生这种错误,而UnicodeError是如果编码或解码 Unicode 有问题,就会发生这种报错。实际上,第二个只是为了说明一点。对于ValueError所有无法将参数转换为浮点数的可信场景,这已经足够了。无论哪种情况,我们都将值"Cannot Calculate"作为我们的结果,并提醒用户不要对硬件提出不合理的要求。

这就是事情变得有趣的地方。仅在未引发异常时else:运行。在这种情况下,如果我们有一个有效的除法计算结果,我们实际上希望将它存储在内存中;相反,如果我们得到“无穷大”或“无法计算”作为我们的结果,我们不会存储它。

无论如何,该finally:部分都会运行。在这种情况下,我们打印出我们的计算结果。

顺序确实很重要。我们必须遵循模式try...except...else...finallyelse如果存在,必须在所有except语句之后。finally总是最后的。

最初很容易混淆elsefinally,因此请确保你了解其中的区别。<strong>else</strong>仅在未引发异常时运行;<strong>finally</strong>每次运行。

Finally执行顺序

你希望以下代码实现什么?

class SonicScrewdriver:

    def __init__(self):
        self.memory = 0

    def perform_division(self, lhs, rhs):
        try:
            result = float(lhs)/float(rhs)
        except ZeroDivisionError:
            print("Wibbly wobbly, timey wimey.")
            result = "Infinity"
        except (ValueError, UnicodeError):
            print("Oy! Don't diss the sonic!")
            result = "Cannot Calculate"
        else:
            self.memory = result
            return result
        finally:
            print(f"Calculation Result: {result}\n")
            result = -1


sonic = SonicScrewdriver()

print(sonic.perform_division(8, 4))

下面的那return句话else应该是事情的结束了吧?其实,不!如果我们运行该代码...

Calculation Result: 2.0

2.0

有两个重要的观察结果:

  1. finally正在运行,即使在我们的return声明之后。该函数不会像通常那样退出。

  2. return语句确实在finally块执行之前运行。我们知道这一点是因为结果result输出是2.0,而不是我们在语句finally中分配的-1

<strong>finally</strong>每次都会运行,即使你<strong>return</strong><strong>try...except</strong>结构中的其他地方有。

但是,我也用一个os.abort()代替测试了上面的return result,在这种情况下,finally块永远不会运行;该程序彻底中止。你可以在任何地方直接停止程序执行,Python 只会放弃它正在做的事情并退出。该规则是不变的,即使是不寻常的finally行为。

抛出异常

所以,我们可以用try...except捕获异常. 但是如果我们只是想主动抛出一个呢?

在 Python 术语中,我们说我们引发了异常,并且与该语言中的大多数事情一样,实现这一点很明显:只需使用raise关键字:

class Tardis:

    def __init__(self):
        pass

    def camouflage(self):
        raise NotImplementedError('Chameleon circuits are stuck.')

tardis = Tardis()
tardis.camouflage()

当我们执行该代码时,我们会看到我们引发的异常。

Traceback (most recent call last):
  File "/home/jason/Code/FiringRanges/PyFiringRange/Sandbox/tardis.py", line 10, in <module>
    tardis.camoflague()
  File "/home/jason/Code/FiringRanges/PyFiringRange/Sandbox/tardis.py", line 7, in camoflague
    raise NotImplementedError('Chameleon circuits are stuck.')
NotImplementedError: Chameleon circuits are stuck.

这样就能知道我们在哪儿出现了异常错误。

注意:异常NotImplementedError是Python 中的内置异常之一,有时用于指示一个函数不应该使用,因为它还没有完成(但总有一天会完成)。它不能与NotImplementedvalue互换。请参阅文档以了解何时使用它们。

显然,关键代码是raise NotImplementedError('Chameleon circuits are stuck.'). 在raise关键字之后,我们给出要引发的异常对象的名称。在大多数情况下,我们从 Exception 类创建一个新对象,从括号的使用可以看出。所有异常都接受字符串作为消息的第一个参数。一些例外接受或需要更多参数,因此请参阅官方文档。

使用异常

有时我们需要在捕捉到异常后对其进行处理。我们有一些非常简单的方法来做到这一点。

最明显的是从异常中打印消息。为此,我们需要能够处理我们捕获的异常对象。让我们将except语句更改为except NotImplementedError as e:,其中e是我们“绑定”到异常对象的名称。然后,我们可以e直接作为对象使用。

tardis = Tardis()

try:
    tardis.camouflage()
except NotImplementedError as e:
    print(e)

异常类已经定义了它的__str__()函数来返回异常消息,所以如果我们将它转换成一个字符串(str()),这就是我们将得到的。你可能还记得上一篇文章print()自动将其参数转换为字符串。当我们运行这段代码时,我们得到...

Chameleon circuits are stuck.

是不是很容易!

冒泡

现在,如果我们想再次引发异常怎么办?

等等,什么?我们刚刚捕获了那个东西。为什么还要再次引发异常?

一个示例是,如果你需要在幕后进行一些清理工作,但最终仍希望调用者必须处理异常。这是一个例子......

class Byzantium:

    def __init__(self):
        self.power = 0

    def gravity_field(self):
        if self.power <= 0:
        raise SystemError("Gravity Failing")


def grab_handle():
    pass


byzantium = Byzantium()

try:
    byzantium.gravity_field()
except SystemError:
    grab_handle()
    print("Night night")
    raise

在上面的示例中,我们只是想捕获一些实体 ( grab_handle()) 并打印一条附加消息,然后让异常继续raise抛出. 当我们重新引发异常时,我们说它冒泡了

Night night
Traceback (most recent call last):
  File "/home/jason/Code/FiringRanges/PyFiringRange/Sandbox/byzantium.py", line 18, in <module>
    byzantium.gravity_field()
  File "/home/jason/Code/FiringRanges/PyFiringRange/Sandbox/byzantium.py", line 8, in gravity_field
    raise SystemError("Gravity Failing")
SystemError: Gravity Failing

注意:你可能认为我们需要说except SystemError as e:或者说raise e什么,但那是矫枉过正。对于冒泡的异常,我们需要自己调用<strong>raise</strong>

现在,如果我们想在冒泡异常的同时添加一些额外的信息怎么办?你的第一个猜测可能只是完全引发一个新异常,但这会带来一些问题。为了演示,我将在执行顺序中添加另一层。请注意,当我处理这个问题时SystemError,我会提出一个新的RuntimeError。我在第二个try...except区块中发现了这个新异常。

byzantium = Byzantium()

def test():
    try:
        byzantium.gravity_field()
    except SystemError:
        grab_handle()
        raise RuntimeError("Night night")

try:
    test()
except RuntimeError as e:
    print(e)
    print(e.__cause__)

当我们运行它时,我们得到以下输出。

Night night
None

当我们捕获到这个新异常时,我们完全没有关于它是什么原因的上下文。为了解决这个问题,Python 3在PEP 3134中引入了显式异常链接。实现它很容易。看看我们的新函数test(),这是我与上一个示例相比唯一更改的部分。

byzantium = Byzantium()

def test():
    try:
        byzantium.gravity_field()
    except SystemError as e:
        grab_handle()
        raise RuntimeError("Night night") from e

try:
    test()
except RuntimeError as e:
    print(e)
    print(e.__cause__)

你有没有发现我在那儿做什么?在except声明中,我将名称绑定e到我们捕获的原始异常。然后,在引发新RuntimeError异常时,我将其链接到上一个异常,并使用from e. 我们现在的输出...

Night night
Gravity Failing

当我们运行它时,我们的新异常会记住它是从哪里来的——前一个异常存储在它的__cause__属性中(打印在输出的第二行)。这对于日志记录特别有用。

你可以使用异常类执行许多其他技巧,尤其是在引入 PEP 3134 时。像往常一样,我建议你阅读文档,我在文章末尾链接到该文档。

自定义异常

Python 有一大堆异常,它们的使用有据可查。当我为工作选择合适的异常时,我经常参考这个异常列表。然而,有时,我们只需要更多……定制的东西。

所有错误类型的异常都是从Exception类派生的,而类又是从BaseException类派生的。这种双重层次结构的原因是你可以捕获所有错误Exceptions,而无需对特殊的、非系统退出的异常(如KeyboardInterrupt. 当然,这在实践中对你来说并不重要,因为except Exception实际上总是我之前提到的反模式的另一种形式。无论如何,建议你直接派生自BaseException——只要知道它存在即可。

在进行自定义异常时,你实际上可以从任何你喜欢的异常类派生。有时,最好从与你正在自定义的异常最接近的异常中获取。但是,如果你不知所措,你可以从Exception派生.

让我们自定义一个,好吗?

class SpacetimeError(Exception):
    def __init__(self, message):
        super().__init__(message)

class Tardis():

    def __init__(self):
        self._destination = ""
        self._timestream = []

    def cloister_bell(self):
        print("(Ominous bell tolling)")

    def dematerialize(self):
        self._timestream.append(self._destination)
        print("(Nifty whirring sound)")

    def set_destination(self, dest):
        if dest in self._timestream:
            self.cloister_bell()
        self._destination = dest

    def engage(self):
        if self._destination in self._timestream:
            raise SpacetimeError("You should not cross your own timestream!")
        else:
            self.dematerialize()


tardis = Tardis()

# Should be fine
tardis.set_destination("7775/349x10,012/acorn")
tardis.engage()

# Also fine
tardis.set_destination("5136/161x298,58/delta")
tardis.engage()

# The TARDIS is not going to like this...
tardis.set_destination("7775/349x10,012/acorn")
tardis.engage()

显然,最后一个将导致我们的SpacetimeError异常被引发。

让我们再看看那个异常类声明。

class SpacetimeError(Exception):
    def __init__(self, message):
        super().__init__(message)

这实际上非常容易编写。如果你还记得我们之前对类的探索,super().__init__()就是在基类上调用初始化函数,Exception在这种情况下就是这样。我们将消息传递给SpacetimeError异常构造函数,并将其交给基类初始化函数。

事实上,如果我唯一要做的就是将 传递messagesuper(), 类,我可以让这更简单:

class SpacetimeError(Exception):
    pass

Python 自己处理基础异常。

这就是我们需要做的所有事情,尽管像往常一样,我们可以用这个做更多的技巧。自定义异常不仅仅是一个漂亮的名字;我们可以使用它们来处理各种不寻常的错误场景,尽管这显然超出了本指南的范围。

Das obige ist der detaillierte Inhalt vonSo lösen Sie Python-Fehlerausnahmen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:yisu.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen