PEP 463 – 异常捕获表达式
- 作者:
- Chris Angelico <rosuav at gmail.com>
- 状态:
- 已拒绝
- 类型:
- 标准跟踪
- 创建:
- 2014年2月15日
- Python 版本:
- 3.5
- 历史记录:
- 2014年2月20日,2014年2月16日
- 决议:
- Python-Dev 消息
拒绝通知
来自 https://mail.python.org/pipermail/python-dev/2014-March/133118.html
“”” 我想拒绝这个 PEP。我认为,鉴于所需的语义,提出的语法是可以接受的,尽管它仍然有点刺耳。它可能不比 lambda 中使用的冒号更糟糕(它与 def 中使用的冒号相呼应,就像此处的冒号与 try/except 中的冒号相呼应一样),并且肯定比列出的替代方案更好。
但是,我无法接受的是动机和基本原理。我不认为例如 dict.get() 在我们有了 except 表达式后就会变得不必要,并且我不同意 EAFP 比 LBYL 更好,或者“通常由 Python 推荐”的观点。(你从哪里得到这个观点?是从那些痴迷于 DRY 以至于宁愿引入高阶函数也不愿重复一行代码的同一个来源吗?:-)
这可能是我所能做出的最明确的表态了。鉴于即将召开的语言峰会,我很乐意在那里更深入地探讨我拒绝它的理由(如果有需求的话)。
我认为(除了从未解释过那些可怕的首字母缩略词 :-) 外,这是一个写得很好且经过充分研究的 PEP,我认为你在主持讨论、收集反对意见、审查替代方案以及将激烈的辩论转化为 PEP 所需的一切方面都做得非常出色。干得好,Chris(以及所有提供帮助的人),并祝你在下一个 PEP 中好运!“””
摘要
就像 PEP 308 引入了在表达式中使用基于值的条件的方法一样,此系统允许将基于异常的条件用作表达式的一部分。
动机
许多函数和方法的参数会导致它们返回指定的值,而不是引发异常。当前的系统是临时且不一致的,并且需要为每个函数单独编写此功能;并非所有函数都支持此功能。
- dict.get(key, default) - 第二个位置参数代替 KeyError
- next(iter, default) - 第二个位置参数代替 StopIteration
- list.pop() - 无法返回默认值
- seq[index] - 无法处理边界错误
- min(sequence, default=default) - 关键字参数代替 ValueError
- statistics.mean(data) - 无法处理空迭代器
如果此功能在 Python 的早期历史中就已经存在,则无需创建 dict.get() 和相关方法;处理缺失键的唯一明显方法是对异常做出响应。编写了一种方法以一种方式发出不存在的信号,并使用了一种一致的技术来响应不存在。相反,我们有 dict.get(),并且从 Python 3.4 开始,我们还有 min(… default=default) 和无数其他方法。我们有用于在表达式内部进行测试的 LBYL 语法,但目前没有 EAFP 表示法;比较以下内容
# LBYL:
if key in dic:
process(dic[key])
else:
process(None)
# As an expression:
process(dic[key] if key in dic else None)
# EAFP:
try:
process(dic[key])
except KeyError:
process(None)
# As an expression:
process(dic[key] except KeyError: None)
Python 通常推荐 EAFP 策略,但随后必须大量使用诸如 dic.get(key,None) 之类的实用函数来启用它。
基本原理
当前系统要求函数作者预测对默认值的需要,并实现对其的支持。如果未执行此操作,则需要完整的 try/except 代码块。
由于 try/except 是一个语句,因此无法在表达式的中间捕获异常。就像 if/else 对条件语句和 lambda 对函数定义的作用一样,这也允许在表达式上下文中进行异常捕获。
这为函数提供了一种简洁且一致的方式来提供默认值:它只需引发适当的异常,然后调用方捕获它。
在某些情况下,可以使用 LBYL 技术(例如,在索引到某个序列之前检查它是否具有足够的长度)。这并非在所有情况下都安全,但由于它通常很方便,因此程序员会倾向于牺牲 EAFP 的安全性以换取 LBYL 的简洁表示法。此外,一些 LBYL 技术(例如,使用三个参数的 getattr)会使代码看起来像文字字符串而不是属性查找,这会影响可读性。方便的 EAFP 表示法解决了所有这些问题。
没有方便的方法来编写辅助函数来执行此操作;最接近的是使用 lambda 的一些丑陋的东西
def except_(expression, exception_list, default):
try:
return expression()
except exception_list:
return default()
value = except_(lambda: 1/x, ZeroDivisionError, lambda: float("nan"))
这很笨拙,并且无法处理多个异常子句;或者 eval
def except_(expression, exception_list, default):
try:
return eval(expression, globals_of_caller(), locals_of_caller())
except exception_list as exc:
l = locals_of_caller().copy()
l['exc'] = exc
return eval(default, globals_of_caller(), l)
def globals_of_caller():
return sys._getframe(2).f_globals
def locals_of_caller():
return sys._getframe(2).f_locals
value = except_("""1/x""",ZeroDivisionError,""" "Can't divide by zero" """)
这甚至更笨拙,并且依赖于特定于实现的技巧。(为除 CPython 之外的解释器编写 globals_of_caller() 和 locals_of_caller() 留给读者作为练习。)
提案
就像“或”运算符和三部分“if-else”表达式提供了捕获虚假值并替换它的短路方法一样,此语法提供了捕获异常并替换它的短路方法。
这目前有效
lst = [1, 2, None, 3]
value = lst[2] or "No value"
该提案添加了以下内容
lst = [1, 2]
value = (lst[2] except IndexError: "No value")
具体来说,提出的语法是
(expr except exception_list: default)
其中 expr、exception_list 和 default 都是表达式。首先,评估 expr。如果未引发异常,则其值为整个表达式的值。如果引发任何异常,则评估 exception_list,并且应产生类型或元组,就像 try/except 语句形式一样。任何匹配的异常将导致相应的 default 表达式被评估并成为表达式的值。与 try/except 语句形式一样,不匹配的异常将向上传播。
需要在整个表达式周围使用括号,除非它们完全冗余,遵循与生成器表达式相同的规则。这保证了嵌套 except 表达式的正确解释,并允许将来扩展语法 - 请参阅下面关于多个 except 子句的内容。
请注意,当前提案不允许捕获异常对象。在需要此功能的地方,必须使用语句形式。(请参阅下面关于此的讨论和详细说明。)
此三元运算符的优先级介于 lambda 和 if/else 之间。
考虑此双层缓存示例
for key in sequence:
x = (lvl1[key] except KeyError: (lvl2[key] except KeyError: f(key)))
# do something with x
这不能重写为
x = lvl1.get(key, lvl2.get(key, f(key)))
尽管它更短,但它违背了缓存的目的,因为它必须计算一个默认值以传递给 get()。.get() 版本向后计算;异常测试版本向前计算,正如预期的那样。最接近的有用等价物将是
x = lvl1.get(key) or lvl2.get(key) or f(key)
这依赖于值不为零,以及缓存对象支持此功能。
替代方案
在 python-ideas 上的讨论提出了以下语法建议
value = expr except default if Exception [as e]
value = expr except default for Exception [as e]
value = expr except default from Exception [as e]
value = expr except Exception [as e] return default
value = expr except (Exception [as e]: default)
value = expr except Exception [as e] try default
value = expr except Exception [as e] continue with default
value = default except Exception [as e] else expr
value = try expr except Exception [as e]: default
value = expr except default # Catches anything
value = expr except(Exception) default # Catches only the named type(s)
value = default if expr raise Exception
value = expr or else default if Exception
value = expr except Exception [as e] -> default
value = expr except Exception [as e] pass default
还建议创建一个新的关键字,而不是重用现有的关键字。此类提案与最后一种形式具有相同的结构,但使用不同的关键字代替“pass”。建议包括“then”、“when”和“use”。此外,在“如果 expr 引发异常则使用默认值”提案的上下文中,建议使用新的关键字“raises”。
出于简单起见,所有涉及“as”捕获子句的形式都已从本提案中延迟,但以上表中保留了这些形式,作为对建议的准确记录。
本提案最支持的四种形式按顺序排列如下
value = (expr except Exception: default)
value = (expr except Exception -> default)
value = (expr except Exception pass default)
value = (expr except Exception then default)
所有四种形式都保持从左到右的求值顺序:首先是基本表达式,然后是异常列表,最后是默认值。这很重要,因为表达式是延迟求值的。相比之下,上面列出的一些临时替代方案必须(根据函数的性质)急切地求值其默认值。首选形式使用冒号,通过使用“except exception_list:”与 try/except 相似,并通过使用“关键字名称列表:子表达式”与 lambda 相似;它还可以被理解为以字典样式将 Exception 映射到默认值。使用箭头会引入许多程序员不熟悉且当前没有类似含义的标记,但在其他方面非常易读。英语单词“pass”具有大致相似的含义(考虑函数参数的常见用法“按值/引用传递”),并且“pass”已经是一个关键字,但由于其含义截然不同,因此可能会造成混淆。使用“then”在英语中是有意义的,但这会在语言中引入一个新的关键字 - 尽管它不是常用关键字,但仍然是一个新的关键字。
从左到右的求值顺序对于可读性至关重要,因为它与大多数表达式求值的顺序相似。诸如
value = (expr except default if Exception)
之类的替代方案打破了这一点,因为它首先求值两端,然后来到中间;虽然这可能看起来并不糟糕(因为异常列表通常是一个常量),但当多个子句相遇时,无论是使用多个 except/if 还是使用现有的 if/else,或者两者结合使用时,都会增加混淆。使用首选顺序,无论语法如何嵌套,子表达式都将始终从左到右求值。
保留现有的表示法,但更改强制括号的位置,我们有以下建议
value = expr except (Exception: default)
value = expr except(Exception: default)
这让人想起函数调用或字典初始化。冒号不会与引入代码块混淆,但另一方面,新语法保证了延迟求值,而字典则没有。减少混淆的可能性被认为不值得相应的增加混淆的可能性。
使用示例
对于每个示例,都给出了一个近似等价的语句形式,以显示表达式将如何被解析。这些并不总是严格等价的,但会达到相同目的。解释器将一个转换为另一个并不安全。
许多这些示例直接来自 Python 标准库,文件名和行号在 2014 年 2 月初是正确的。许多这些模式非常常见。
检索参数,默认为 None
cond = (args[1] except IndexError: None)
# Lib/pdb.py:803:
try:
cond = args[1]
except IndexError:
cond = None
如果可用,则从系统中获取信息
pwd = (os.getcwd() except OSError: None)
# Lib/tkinter/filedialog.py:210:
try:
pwd = os.getcwd()
except OSError:
pwd = None
尝试翻译,回退到原文
e.widget = (self._nametowidget(W) except KeyError: W)
# Lib/tkinter/__init__.py:1222:
try:
e.widget = self._nametowidget(W)
except KeyError:
e.widget = W
从迭代器中读取,一旦迭代器耗尽,则继续使用空行
line = (readline() except StopIteration: '')
# Lib/lib2to3/pgen2/tokenize.py:370:
try:
line = readline()
except StopIteration:
line = ''
检索特定于平台的信息(注意 DRY 改进);此特定示例可以进一步扩展,将一系列单独的赋值转换为单个大型字典初始化
# sys.abiflags may not be defined on all platforms.
_CONFIG_VARS['abiflags'] = (sys.abiflags except AttributeError: '')
# Lib/sysconfig.py:529:
try:
_CONFIG_VARS['abiflags'] = sys.abiflags
except AttributeError:
# sys.abiflags may not be defined on all platforms.
_CONFIG_VARS['abiflags'] = ''
检索索引项,默认为 None(类似于 dict.get)
def getNamedItem(self, name):
return (self._attrs[name] except KeyError: None)
# Lib/xml/dom/minidom.py:573:
def getNamedItem(self, name):
try:
return self._attrs[name]
except KeyError:
return None
将数字转换为名称,回退到数字
g = (grp.getgrnam(tarinfo.gname)[2] except KeyError: tarinfo.gid)
u = (pwd.getpwnam(tarinfo.uname)[2] except KeyError: tarinfo.uid)
# Lib/tarfile.py:2198:
try:
g = grp.getgrnam(tarinfo.gname)[2]
except KeyError:
g = tarinfo.gid
try:
u = pwd.getpwnam(tarinfo.uname)[2]
except KeyError:
u = tarinfo.uid
查找属性,回退到默认值
mode = (f.mode except AttributeError: 'rb')
# Lib/aifc.py:882:
if hasattr(f, 'mode'):
mode = f.mode
else:
mode = 'rb'
return (sys._getframe(1) except AttributeError: None)
# Lib/inspect.py:1350:
return sys._getframe(1) if hasattr(sys, "_getframe") else None
以 EAFP 模式执行一些冗长的计算,将除以零处理为一种粘性 NaN
value = (calculate(x) except ZeroDivisionError: float("nan"))
try:
value = calculate(x)
except ZeroDivisionError:
value = float("nan")
计算一系列数字的平均值,回退到零
value = (statistics.mean(lst) except statistics.StatisticsError: 0)
try:
value = statistics.mean(lst)
except statistics.StatisticsError:
value = 0
在稀疏的覆盖列表中查找对象
(overrides[x] or default except IndexError: default).ping()
try:
(overrides[x] or default).ping()
except IndexError:
default.ping()
缩小异常捕获范围
以下示例直接来自 Python 的标准库,演示了如何方便地缩小 try/except 的范围。要使用 try/except 语句形式执行此操作,需要一个临时变量,但作为表达式要干净得多。
Lib/ipaddress.py:343
try:
ips.append(ip.ip)
except AttributeError:
ips.append(ip.network_address)
变为
ips.append(ip.ip except AttributeError: ip.network_address)
表达式的形式几乎等价于此
try:
_ = ip.ip
except AttributeError:
_ = ip.network_address
ips.append(_)
Lib/tempfile.py:130
try:
dirlist.append(_os.getcwd())
except (AttributeError, OSError):
dirlist.append(_os.curdir)
变为
dirlist.append(_os.getcwd() except (AttributeError, OSError): _os.curdir)
Lib/asyncore.py:264
try:
status.append('%s:%d' % self.addr)
except TypeError:
status.append(repr(self.addr))
变为
status.append('%s:%d' % self.addr except TypeError: repr(self.addr))
在每种情况下,try/except 的范围缩小,确保意外异常(例如,如果“append”拼写错误导致的AttributeError)不会被相同的处理程序捕获。这种情况发生的可能性足够小,不至于需要将调用拆分成单独的行(如上面五行示例所示),但它是转换带来的一个小好处。
与其他语言的比较
(感谢 Andrew Barnert 编译了本节内容。请注意,此处提供的示例不反映提案的当前版本,需要进行编辑。)
Ruby的“begin…rescue…rescue…else…ensure…end”是一个表达式(可能包含语句)。它具有等效的“as”子句和等效的裸except。并且它在裸except/异常类/带as子句的异常类和值之间不使用任何标点符号或关键字。(是的,除非你理解 Ruby 的语句/表达式规则,否则它是模棱两可的。)
x = begin computation() rescue MyException => e default(e) end;
x = begin computation() rescue MyException default() end;
x = begin computation() rescue default() end;
x = begin computation() rescue MyException default() rescue OtherException other() end;
就本 PEP 而言
x = computation() except MyException as e default(e)
x = computation() except MyException default(e)
x = computation() except default(e)
x = computation() except MyException default() except OtherException other()
Erlang 有一个看起来像这样的 try 表达式
x = try computation() catch MyException:e -> default(e) end;
x = try computation() catch MyException:e -> default(e); OtherException:e -> other(e) end;
类和“as”名称是必须的,但你可以对两者都使用“_”。每个还有可选的“when”保护,以及你可以捕获的“throw”子句,我就不详细介绍了。要处理多个异常,只需用分号分隔子句,我想这在 Python 中会映射为逗号。所以
x = try computation() except MyException as e -> default(e)
x = try computation() except MyException as e -> default(e), OtherException as e->other_default(e)
Erlang 还有一个“catch”表达式,尽管使用了相同的关键字,但它完全不同,你也不想知道它。
ML 家族有两种不同的处理方法,“handle”和“try”;两者之间的区别在于“try”对异常进行模式匹配,这让你能够获得多个 except 子句和 as 子句的效果。在任何一种形式中,处理程序子句都用某些方言中的“=>”或其他方言中的“->”分隔。
为了避免混淆,我将以 Python 风格编写函数调用。
这是 SML 的“handle”
let x = computation() handle MyException => default();;
这是 OCaml 的“try”
let x = try computation() with MyException explanation -> default(explanation);;
let x = try computation() with
MyException(e) -> default(e)
| MyOtherException() -> other_default()
| (e) -> fallback(e);;
就本 PEP 而言,这些将类似于
x = computation() except MyException => default()
x = try computation() except MyException e -> default()
x = (try computation()
except MyException as e -> default(e)
except MyOtherException -> other_default()
except BaseException as e -> fallback(e))
许多受 ML 启发但与之不直接相关的学术语言将事物混合在一起,通常使用更多关键字和更少符号。因此,Oz 将映射到 Python 为
x = try computation() catch MyException as e then default(e)
许多 Lisp 派生语言,如 Clojure,将 try/catch 实现为特殊形式(如果你不知道这意味着什么,请将其视为类似函数的宏),因此你实际上编写的是
try(computation(), catch(MyException, explanation, default(explanation)))
try(computation(),
catch(MyException, explanation, default(explanation)),
catch(MyOtherException, explanation, other_default(explanation)))
在 Common Lisp 中,这是通过稍微笨拙的 “handler-case”宏 完成的,但基本思想相同。
令人惊讶的是,Lisp 风格被一些没有宏的语言使用,例如 Lua,其中 xpcall 接收函数。以 Python 风格而不是 Lua 风格编写 lambda
x = xpcall(lambda: expression(), lambda e: default(e))
这实际上返回 (true, expression()) 或 (false, default(e)),但我认为我们可以忽略这部分。
Haskell 在这里实际上类似于 Lua(除了它都是用单子完成的,当然)
x = do catch(lambda: expression(), lambda e: default(e))
你可以在函数中编写一个模式匹配表达式来决定如何处理它;捕获和重新引发你不想捕获的异常足够便宜,可以成为惯用法。
但是 Haskell 的中缀表示法使它更漂亮
x = do expression() `catch` lambda: default()
x = do expression() `catch` lambda e: default(e)
这使得提案中 lambda 冒号和 except 冒号之间的并行性更加明显
x = expression() except Exception: default()
x = expression() except Exception as e: default(e)
Tcl 拥有 Lua 的 xpcall 的另一半;catch 是一个函数,如果捕获到异常则返回 true,否则返回 false,你还可以通过其他方式获取值。并且它都是围绕着 Tcl 中所有内容都基于的隐式引用和执行构建的,使其比 Lisp 宏更难以用 Python 术语描述,但类似于
if {[ catch("computation()") "explanation"]} { default(explanation) }
Smalltalk 也难以映射到 Python。基本版本将是
x := computation() on:MyException do:default()
…但这基本上是 Smalltalk 的带冒号的传递参数语法,而不是它的异常处理语法。
延迟的子提案
多个 except 子句
对用例的检查表明,与语句形式相比,这并不像预期的那样经常需要,并且由于其语法是尚未达成共识的一点,因此整个特性被推迟。
可以使用多个“except”关键字,它们都将捕获原始表达式中引发的异常(仅此而已)
# Will catch any of the listed exceptions thrown by expr;
# any exception thrown by a default expression will propagate.
value = (expr
except Exception1: default1
except Exception2: default2
# ... except ExceptionN: defaultN
)
目前,必须使用以下形式之一
# Will catch an Exception2 thrown by either expr or default1
value = (
(expr except Exception1: default1)
except Exception2: default2
)
# Will catch an Exception2 thrown by default1 only
value = (expr except Exception1:
(default1 except Exception2: default2)
)
列出多个没有括号的异常子句是语法错误(见上文),因此 Python 的未来版本可以在不破坏任何现有代码的情况下添加此功能。
捕获异常对象
在 try/except 块中,使用“as”捕获异常对象会创建一个局部名称绑定,并在 finally 子句中隐式删除该绑定(以避免创建引用循环)。在表达式上下文中,这没有多大意义,需要一个合适的子作用域来安全地捕获异常对象——类似于列表推导式的处理方式。但是,CPython 目前使用嵌套函数调用来实现推导式的子作用域,这在某些上下文中(例如类定义)会产生影响,因此不适合本提案。如果将来有办法创建真正的子作用域(这可以简化推导式、except 表达式、带块的表达式等等),那么本提案可以重新提出;在此之前,它的损失并不大,因为适用于此处使用的表达式表示法的简单异常处理通常只关心异常的类型,而不关心其值——下面的分析将进一步说明。
诚然,此语法将允许以方便的方式在交互式 Python 中捕获异常;返回值由“_”捕获,但异常目前没有。这可以拼写为
>>> (expr except Exception as e: e)
对 Python 标准库的检查表明,虽然“as”的使用相当普遍(大约在五分之一的 except 子句中出现),但在可以逻辑上转换为表达式形式的情况下,它却极其不常见。它的少数用法可以简单地保持不变。因此,为了简单起见,本提案中不包含“as”子句。随后的 Python 版本可以在不破坏任何现有代码的情况下添加此功能,因为“as”已经是关键字。
一个可能很有用的示例是 Lib/imaplib.py:568
try: typ, dat = self._simple_command('LOGOUT')
except: typ, dat = 'NO', ['%s: %s' % sys.exc_info()[:2]]
这可以变成
typ, dat = (self._simple_command('LOGOUT')
except BaseException as e: ('NO', '%s: %s' % (type(e), e)))
或者可能是其他一些变体。这绝不是最引人注目的用例,但对这段代码进行智能分析可以显著地清理它。由于缺乏其他示例来证明对异常对象的任何需求,我选择无限期地推迟建议。
被拒绝的子提案
finally 子句
语句形式 try…finally 或 try…except…finally 没有逻辑上对应的表达式形式。因此,finally 关键字无论如何都不是本提案的一部分。
空 except 具有不同的含义
在几个提议的语法中,省略异常类型名称将很容易且简洁,并且很诱人。为了方便起见,拥有一个裸“except”子句意味着比“except BaseException”更有用的东西可能是有利的。提案包括让它捕获 Exception,或一组特定的“常见异常”(新类型 ExpressionError 的子类),或者让它在当前作用域中查找名为 ExpressionError 的元组,并使用内置的默认值,例如 (ValueError, UnicodeError, AttributeError, EOFError, IOError, OSError, LookupError, NameError, ZeroDivisionError)。所有这些都被拒绝了,原因有几个。
- 首先,它将破坏与 try/except 语句形式的一致性。就像列表推导式或三元 if 表达式可以通过“将其分解”为其垂直语句形式来解释一样,expression-except 应该能够通过相对机械的翻译转换为几乎等效的语句。因此,两种形式中任何常见的语法都应该在每种形式中具有相同的语义,最重要的是不应该在一种形式中捕获更多内容而在另一种形式中捕获更少内容,因为它会倾向于吸引未被注意到的错误。
- 其次,要捕获的适当异常集本身将是一个巨大的争论点。不可能准确预测哪些异常“有意义”地被捕获;为什么用方便的语法来祝福其中一些,而不用于其他一些?
- 最后(部分原因是建议是,一旦将裸except 简化为“合理的”异常集,就应该积极鼓励使用它),在任何你捕获了你预计不会捕获的异常的情况下,都是一个不必要的错误磁铁。
因此,裸“except”的使用有两种可能性:要么在表达式形式中在语法上禁止它,要么允许它与语句形式具有完全相同的语义(即,它捕获 BaseException 并且无法使用“as”捕获它)。
空 except 子句
PEP 8 正确地建议避免使用裸“except”。虽然它在语句中在语法上是合法的,并且为了向后兼容性必须保持这种状态,但鼓励使用它的价值很小。在表达式 except 子句中,“except:”是 SyntaxError;请改用等效的长格式“except BaseException:”。Python 的未来版本 MAY 选择恢复此功能,这可以在不破坏兼容性的情况下完成。
except 子句周围使用括号
是否应该允许将 except 子句括起来,与可能引发异常的表达式分开?例如
value = expr (
except Exception1 [as e]: default1
except Exception2 [as e]: default2
# ... except ExceptionN [as e]: defaultN
)
当包含多个 except 子句和/或异常捕获的两个或其中一个延迟的子提案时,这一点更具说服力。在它们不存在的情况下,括号将是这样的
value = expr except ExceptionType: default
value = expr (except ExceptionType: default)
优势很小,并且可能使读者误以为 except 子句与表达式分离,或者误以为这是一个函数调用,这使得它不具有吸引力。当然,如果需要,表达式可以加上括号,默认值也可以
value = (expr) except ExceptionType: (default)
由于现在整个表达式都需要放在括号中(在讨论此事时尚未决定),因此减少了描述此部分的需要,并且在许多情况下它将是冗余的。
“except: pass” 的简写
以下内容已被建议作为类似的简写,尽管在技术上不是表达式
statement except Exception: pass
try:
statement
except Exception:
pass
例如,一个常见的用例是尝试删除文件
os.unlink(some_file) except OSError: pass
但是,Python 3.4 中的 contextlib 已经存在等效项
from contextlib import suppress
with suppress(OSError): os.unlink(some_file)
由于这已经是单行(或带冒号换行后的两行),因此无需使用新的语法并将语句与表达式混淆来实现此目的。
常见反对意见
冒号始终引入代码块
虽然 Python 的许多语法元素确实使用冒号来引入语句块(if、while、with、for 等),但这绝不是冒号的唯一用途。目前,Python 语法包括四种情况,其中冒号引入子表达式
- 字典显示 - { … key:value … }
- 切片表示法 - [start:stop:step]
- 函数定义 - 参数 : 注解
- lambda - 参数列表: 返回值
本提案只是添加了第五种
- except-表达式 - 异常列表: 结果
样式指南和PEP 8 应该建议不要在换行符的末尾使用冒号,因为这可能看起来像是套件的开头,而是提倡在异常列表之前换行,使冒号清晰地位于两个表达式之间。
版权
本文档已进入公有领域。
来源:https://github.com/python/peps/blob/main/peps/pep-0463.rst