Python dynamically catch exceptions

高洛峰
Release: 2016-10-18 11:01:59
Original
1346 people have browsed it

One of the things that blew my mind when discussing dynamically catching exceptions is that it allows me to find hidden bugs and fun...

The code in question


The code below is from a product that looks like good abstraction code - slightly(!) . This is a function that calls some statistical data and then processes it. The first is to use a socket connection to get a value. A socket error may have occurred. Since the statistical data is not critical in the system, we just write it down. Log errors and keep going.

(Please note that I tested this article using doctest - this means the code can run!)

>>> def get_stats():

... pass

.. .

>>> def do_something_with_stats(stats):

... pass

...

>>> try:

... stats = get_stats()

... except socket.error:

... logging.warning("Can't get statistics")

... else:

... do_something_with_stats(stats)

Find


We found nothing wrong during testing, But actually we noticed that the static analysis report showed an issue:

$ flake8 filename.py

filename.py:351:1: F821 undefined name 'socket'

filename.py:352:1: F821 undefined name ' logging'

Obviously we did not test it. The problem is that we did not reference the socket and logging modules in the code. What surprised me was that this did not throw a NameError in advance. I thought it would look for these exception statements. Some nouns, such as it needs to catch these exceptions, what does it need to know!

It turns out that this is not the case, the search for exception statements is completed lazily, and only an exception is thrown during evaluation. Not only the name lazy search, but also can be customized Explicitly declare exceptions as 'argument'.

This can be a good thing, a bad thing, or a nuisance.

Good thing (mentioned in the previous paragraph)

Exception parameters can be passed as numeric values ​​in any form. This way This allows abnormal dynamic parameters to be captured.

>>> def do_something():

... blob

...

>>> def attempt(action, ignore_spec):

... try :

... action()

... except ignore_spec:

... pass

...

>>> attempt(do_something, ignore_spec=(NameError, TypeError))

>>> attempt(do_something, ignore_spec=TypeError)

Traceback (most recent call last):

...

NameError: global name 'blob' is not defined

Bad thing (mentioned in the previous paragraph)

This is obvious The disadvantage is that errors in exception parameters are usually noticed only after the exception is triggered, but it is too late. When using exceptions to capture uncommon events (for example: failure to open a file for writing), unless you make a A specific test case, which would otherwise only know when an exception (or any exception) is triggered, logs it and sees if there is a matching exception, and throws its own error exception - which is what a NameError usually means Things to do.

>>> def do_something():

... return 1, 2

...

>>> try:

... a, b = do_something()

. .. except ValuError: # oops - someone can't type

... print("Oops")

... else:

... print("OK!") # we are 'ok' until do_something returns a triple...

OK!

Annoying (mentioned in the previous paragraph)

>>> try:

... TypeError = ZeroDivisionError # now why would we do this...? !

... 1 / 0

... except TypeError:

... print("Caught!")

... else:

... print("ok")

. ..

Caught!

Not only are exception parameters looked up by name, - other expressions work like this too:

>>> try:

... 1 / 0

... except eval( ''.join('Zero Division Error'.split())):

... print("Caught!")

... else:

... print("ok")

. ..

Caught!

Exception parameters can not only be determined at runtime, it can even use exception information during the life cycle. The following is a more convoluted way to catch thrown exceptions - but it can only That’s it:

>>> import sys

>>> def current_exc_type():

... Return sys.exc_info()[0]

...

>>> try:

.. . blob

... except current_exc_type():

... print ("Got you!")

...

Got you!

Obviously this is what we are really looking for when we write This

(byte) code

is the first thing we should think of when doing an exception handler. To confirm how it works in exception handling, I ran dis.dis() on an exception example. ( Note that the decomposition here is under Python 2.7 - different bytecode is generated under Python 3.3, but this is basically similar):

>>> import dis

>>> def x():

... try:

... pass

... except Blobbity:

... print("bad")

...     else:

...         print("good")

...

>>> dis.dis(x)  # doctest: +NORMALIZE_WHITESPACE

 2           0 SETUP_EXCEPT             4 (to 7)

 3           3 POP_BLOCK

             4 JUMP_FORWARD            22 (to 29)

 4     >>    7 DUP_TOP

             8 LOAD_GLOBAL              0 (Blobbity)

            11 COMPARE_OP              10 (exception match)

            14 POP_JUMP_IF_FALSE       28

            17 POP_TOP

            18 POP_TOP

            19 POP_TOP

 5          20 LOAD_CONST               1 ('bad')

            23 PRINT_ITEM

            24 PRINT_NEWLINE

            25 JUMP_FORWARD             6 (to 34)

       >>   28 END_FINALLY

 7     >>   29 LOAD_CONST               2 ('good')

            32 PRINT_ITEM

            33 PRINT_NEWLINE

       >>   34 LOAD_CONST               0 (None)

            37 RETURN_VALUE

这显示出了我原来预期的问题(issue). 异常处理"看起来"完全是按照Python内部机制在运行. 这一步完全没有必要知道关于后续的异常“捕获”语句, 并且如果没有异常抛出它们将被完全忽略了.SETUP_EXCEPT并不关心发生了什么, 仅仅是如果发生了异常, 第一个处理程序应该被评估,然后第二个,以此类推.

每个处理程序都有两部分组成: 获得一个异常的规则, 和刚刚抛出的异常进行对比. 一切都是延迟的, 一切看起来正如对你的逐行的代码的预期一样, 从解释器的角度来考虑. 没有任务聪明的事情发生了,只是突然使得它看起来非常聪明.

总结

虽然这种动态的异常参数让我大吃一惊, 但是这当中包含很多有趣的应用. 当然去实现它们当中的许多或许是个馊主意,呵呵

有时并不能总是凭直觉来确认有多少Python特性的支持 - 例如 在类作用域内 表达式和声明都是被显式接受的, (而不是函数, 方法, 全局作用域),但是并不是所有的都是如此灵活的. 虽然(我认为)那将是十分美好的, 表达式被禁止应用于装饰器 - 以下是Python语法错误:

@(lambda fn: fn)

def x():

  pass

这个是尝试动态异常参数通过给定类型传递给第一个异常的例子, 静静的忍受重复的异常:

>>> class Pushover(object):

...     exc_spec = set()

...

...     def attempt(self, action):

...         try:

...             return action()

...         except tuple(self.exc_spec):

...             pass

...         except BaseException as e:

...             self.exc_spec.add(e.__class__)

...             raise

...

>>> pushover = Pushover()

>>>

>>> for _ in range(4):

...     try:

...         pushover.attempt(lambda: 1 / 0)

...     except:

...         print ("Boo")

...     else:

...         print ("Yay!")

Boo

Yay!

Yay!

Yay!



Related labels:
source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template