为什么在except子句中使用“或”不会引起SyntaxError?有有效的用途吗?


11

在工作中,我偶然发现了一个except带有or运算符的子句:

try:
    # Do something.
except IndexError or KeyError:
    # ErrorHandling

我知道异常类应该作为元组传递,但是它使我感到困惑,甚至不会引起SyntaxError

所以首先我想研究一下它是否真的有效。事实并非如此。

>>> def with_or_raise(exc):
...     try:
...         raise exc()
...     except IndexError or KeyError:
...         print('Got ya!')
...

>>> with_or_raise(IndexError)
Got ya!

>>> with_or_raise(KeyError)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in with_or_raise
KeyError

因此,它没有捕获第二个异常,而查看字节码,原因就更加清楚了:

>>> import dis
>>> dis.dis(with_or_raise)
  2           0 SETUP_EXCEPT            10 (to 12)

  3           2 LOAD_FAST                0 (exc)
              4 CALL_FUNCTION            0
              6 RAISE_VARARGS            1
              8 POP_BLOCK
             10 JUMP_FORWARD            32 (to 44)

  4     >>   12 DUP_TOP
             14 LOAD_GLOBAL              0 (IndexError)
             16 JUMP_IF_TRUE_OR_POP     20
             18 LOAD_GLOBAL              1 (KeyError)
        >>   20 COMPARE_OP              10 (exception match)
             22 POP_JUMP_IF_FALSE       42
             24 POP_TOP
             26 POP_TOP
             28 POP_TOP

  5          30 LOAD_GLOBAL              2 (print)
             32 LOAD_CONST               1 ('Got ya!')
             34 CALL_FUNCTION            1
             36 POP_TOP
             38 POP_EXCEPT
             40 JUMP_FORWARD             2 (to 44)
        >>   42 END_FINALLY
        >>   44 LOAD_CONST               0 (None)
             46 RETURN_VALUE

因此我们可以看到,指令14首先将IndexError类加载到堆栈上。然后,它检查该值是否为True,这是由于Python的真实性所致,最后直接跳转至完成的指令20 exception match。由于指令18被跳过,KeyError因此从未加载到堆栈中,因此不匹配。

我尝试使用Python 2.7和3.6,结果相同。

但是,为什么它是有效的语法?我认为它是以下之一:

  1. 这是来自非常老版本的Python的工件。
  2. 实际上,orexcept子句中使用有一个有效的用例。
  3. 这只是Python解析器的限制,它可能必须接受except关键字之后的任何表达式。

我的投票是3(假设我看到了有关Python新解析器的讨论),但我希望有人能证实这一假设。因为例如如果是2,我想知道该用例!

另外,我对如何继续进行探索一无所知。我想我必须深入研究CPython解析器的源代码,但是在idk哪里可以找到它,也许还有更简单的方法?

Answers:


7

在中except ee可以是任何有效的Python表达式:

try1_stmt ::=  "try" ":" suite
               ("except" [expression ["as" identifier]] ":" suite)+
               ...

[..]对于except带有表达式的子句,将对该表达式求值,并且如果结果对象与该异常“兼容”,则该子句与该异常匹配。如果对象是异常对象的类或基类,或者是包含与异常兼容的项的元组,则该对象与异常兼容。

https://docs.python.org/3/reference/compound_stmts.html#the-try-statement

表达式IndexError or KeyError产生值IndexError。因此,这等效于:

except IndexError:
   ...

谢谢您的快速解答!我们是否会认为它是解析器的局限性(即只能接受任何表达式而不是更具体的表达式)还是故意选择的,以便不对内容进行过多限制?
卢瓦克·特谢拉

1
在我看来,坚持简单的Python更好的基本原则。为什么在接受任何表示意味着没有新的特殊情况的完全自由的情况下发明只能限制的新规则?
deceze

这是一个故意的选择,它允许人们组合一堆异常以动态捕获,并在except语句中使用这样的值。
user4815162342

我想限制这种可能性的原因将是阻止开发人员编写不符合他们预期目的的代码。毕竟,写作except IndexError or KeyError看起来像是一件体面的事情。但是,我同意您的观点,Python会尝试尊重其他一些价值观。
卢瓦克·特谢拉

请注意,python还允许var == 1 or 2,这对未经训练的人来说也“看起来像是一件体面的事情”。
艾瑞克(Eric)

-2

您应该使用类型的n元组而不是逻辑表达式(后者仅返回第一个非false元素):

def with_or_raise(exc):
  try:
    raise exc()
  except (IndexError,KeyError):
    print('Got ya!')

如问题中所述,我知道异常类应该作为元组传递,但是我想知道为什么使用or仍然是有效的Python。
卢瓦克·特谢拉
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.