PEP 3136 – Labeled break and continue
- 作者:
- Matt Chisholm <matt-python at theory.org>
- 状态:
- 已拒绝
- 类型:
- 标准跟踪
- 创建日期:
- 2007年6月30日
- Python 版本:
- 3.1
- 发布历史:
拒绝通知
此 PEP 已被拒绝。请参见 https://mail.python.org/pipermail/python-3000/2007-July/008663.html。
摘要
本 PEP 提议为 Python 的 break 和 continue 语句添加对标签的支持。其灵感来源于其他语言中的带标签的 break 和 continue,以及作者本人偶尔但持续存在对此类功能的需要。
引言
break 语句允许程序员提前终止循环,而 continue 语句允许程序员提前进入循环的下一次迭代。在当前的 Python 中,break 和 continue 只能应用于最内层的包含循环。
为 break 和 continue 语句添加标签支持是对 break 和 continue 语句现有行为的逻辑扩展。带标签的 break 和 continue 可以提高使用嵌套循环的复杂代码的可读性和灵活性。
为简洁起见,本 PEP 中的示例和讨论通常会提到 break 语句。然而,所有示例和动机同样适用于带标签的 continue。
动机
如果程序员希望进入外部包含循环的下一次迭代,或者一次性终止多个循环,他/她只有几种不太优雅的选择。
这是在 Python 中模仿带标签的 break 的一种常见方法(对于此示例及后续示例,... 表示任意数量的中间代码行)。
for a in a_list:
time_to_break_out_of_a = False
...
for b in b_list:
...
if condition_one(a, b):
break
...
if condition_two(a, b):
time_to_break_out_of_a = True
break
...
if time_to_break_out_of_a:
break
...
这需要五行代码和一个额外的变量 time_to_break_out_of_a 来跟踪何时跳出外部 (a) 循环。而这五行代码分散在许多代码行中,使得控制流难以理解。
这种技术也很容易出错。修改此代码的程序员可能会不小心将新代码放在内部 (b) 循环的末尾之后但 time_to_break_out_of_a 测试之前,而不是放在测试之后。这意味着本应通过跳出外部循环而被跳过的代码会被错误地执行。
这也可以用异常来编写。程序员将声明一个特殊异常,将内部循环包装在 try 块中,并在看到异常时捕获并 break。
class BreakOutOfALoop(Exception): pass
for a in a_list:
...
try:
for b in b_list:
...
if condition_one(a, b):
break
...
if condition_two(a, b):
raise BreakOutOfALoop
...
except BreakOutOfALoop:
break
...
但是,同样,这需要五行代码和一个新的、单用途的异常类(而不是一个新的变量),并将基本控制流分散在多行中。它使用 break 跳出内部循环,并通过异常跳出另一个循环,这很不优雅。[1]
下一个策略可能是最优雅的解决方案,假设 condition_two() 的计算成本不高。
for a in a_list:
...
for b in b_list:
...
if condition_one(a, b):
break
...
if condition_two(a, b):
break
...
if condition_two(a, b)
break
...
两次 break 仍然很不优雅。此实现还依赖于这样一个事实:内部 (b) 循环会将 b 泄露到外部 for 循环中,这(尽管明确支持)对新手来说是令人惊讶的,并且在我看来是反直觉且糟糕的做法。
程序员还必须记住在条件 two 下放两个 break,并且不要在第二个 break 之前插入代码。单一的概念性操作,即在 condition_two() 时跳出两个循环,需要在两个缩进级别上编写四行代码,可能在内部 (b) 循环的末尾被许多中间行分隔。
其他语言
现在,请暂时放下您可能对其他编程语言的任何不喜欢,并考虑带标签的 break 和 continue 的语法。在 Perl 中
ALOOP: foreach $a (@a_array){
...
BLOOP: foreach $b (@b_array){
...
if (condition_one($a,$b)){
last BLOOP; # same as plain old last;
}
...
if (condition_two($a,$b)){
last ALOOP;
}
...
}
...
}
(注:Perl 使用 last 而不是 break。BLOOP 标签可以省略;last 和 continue 默认应用于最内层的循环。)
PHP 使用一个数字来表示要跳出的循环次数,而不是标签。
foreach ($a_array as $a){
....
foreach ($b_array as $b){
....
if (condition_one($a, $b)){
break 1; # same as plain old break
}
....
if (condition_two($a, $b)){
break 2;
}
....
}
...
}
C/C++、Java 和 Ruby 都具有类似的构造。
关于何时跳出外部 (a) 循环的控制流完全封装在 break 语句中,该语句在满足 break 条件时执行。break 语句的深度无关紧要。控制流不会分散。不需要额外的变量、异常或重新检查/存储控制条件。不存在因在内部 (b) 循环末尾和在外部 (a) 循环内重新检查 break 条件之间无意中插入新代码的危险。这些就是带标签的 break 和 continue 将为 Python 带来的好处。
本 PEP 不是什么
本 PEP 不是添加 GOTO 到 Python 的提议。GOTO 允许程序员跳转到任意代码块或代码行,通常会使控制流更难理解。尽管 break 和 continue(无论是否支持标签)都可以被视为一种 GOTO,但它受到很大的限制。另一个 Python 构造 yield 也可以被视为一种 GOTO——一种限制性更小的 GOTO。本 PEP 的目标是为现有的控制流工具 break 和 continue 提出扩展,以使控制流更易于理解,而不是更难。
带标签的 break 和 continue 无法将控制转移到另一个函数或方法。它们甚至无法将控制转移到当前作用域中的任意代码行。目前,它们只能影响循环的行为,并且与 GOTO 非常不同,限制也大得多。此扩展允许它们影响当前命名空间中的任何包含循环,但它不会将它们的行为更改为 GOTO。
规范
在所有这些提议下,break 和 continue 本身将继续按当前方式行为,默认应用于最内层的循环。
方案 A - 显式标签
for 和 while 循环语法将后跟一个可选的 as 或 label(上下文关键字)[2],然后是一个标识符,该标识符可用于标识要从中 break(或要 continue)的循环。
break(和 continue)语句将后跟一个可选的标识符,该标识符引用要从中 break(或要 continue)的循环。这是一个使用 as 关键字的示例
for a in a_list as a_loop:
...
for b in b_list as b_loop:
...
if condition_one(a, b):
break b_loop # same as plain old break
...
if condition_two(a, b):
break a_loop
...
...
或者,使用 label 代替 as
for a in a_list label a_loop:
...
for b in b_list label b_loop:
...
if condition_one(a, b):
break b_loop # same as plain old break
...
if condition_two(a, b):
break a_loop
...
...
这具有上述所有优点。它需要修改语言语法:break 和 continue 语句以及 for 和 while 语句的语法。它需要一个新的条件关键字 label 或对条件关键字 as 的扩展。[3] 它不太可能需要对现有 Python 程序进行任何更改。将未在本地作用域中定义的标识符传递给 break 或 continue 会引发 NameError。
方案 B - 数字 break & continue
与其修改 for 和 while 循环的语法,不如让 break 和 continue 接受一个数字参数,表示被控制的包含循环,类似于 PHP。
在我看来,让 break 和 continue 引用从零开始索引的循环,而不是从一开始索引的循环(如 PHP 所做),这样更具 Python 风格。
for a in a_list:
...
for b in b_list:
...
if condition_one(a,b):
break 0 # same as plain old break
...
if condition_two(a,b):
break 1
...
...
传递一个过大、过小或非整数的数字给 break 或 continue 会(可能)引发 IndexError。
此提案不需要对现有 Python 程序进行任何更改。
方案 C - 重复方法
为 break 和 continue 语法将被修改,以允许在同一行上有多个 break 和 continue 语句。因此,break break 将跳出第一个和第二个包含循环。
for a in a_list:
...
for b in b_list:
...
if condition_one(a,b):
break # plain old break
...
if condition_two(a,b):
break break
...
...
这还将允许程序员通过简单地编写 break continue 来跳出内部循环并继续下一个最外层循环,[4] 等等。我不确定如果程序员使用的 break 或 continue 语句比现有循环多,会引发什么异常(可能是 SyntaxError?)。
我预计此提案会被拒绝,因为它会被认为太难理解。
此提案不需要对现有 Python 程序进行任何更改。
方案 D - 显式迭代器
与其用标签来装饰 for 和 while 循环语法,不如要求希望使用带标签 break 的程序员显式创建迭代器,并在希望从更深层的循环中 break 出或 continue 该循环时将其分配给一个标识符。
a_iter = iter(a_list)
for a in a_iter:
...
b_iter = iter(b_list)
for b in b_iter:
...
if condition_one(a,b):
break b_iter # same as plain old break
...
if condition_two(a,b):
break a_iter
...
...
将非迭代器对象传递给 break 或 continue 会引发 TypeError;不存在的标识符会引发 NameError。此提案只需要额外一行代码来创建带标签的循环,无需额外行即可跳出包含循环,也无需更改现有 Python 程序。
方案 E - 显式迭代器和迭代器方法
这是方案 D 的一个变体。如果需要比最基本使用 break 和 continue 之外的任何内容,则需要显式创建迭代器。而不是修改 break 和 continue 的语法,而是可以将 .break() 和 .continue() 方法添加到 Iterator 类型。
a_iter = iter(a_list)
for a in a_iter:
...
b_iter = iter(b_list)
for b in b_iter:
...
if condition_one(a,b):
b_iter.break() # same as plain old break
...
if condition_two(a,b):
a_iter.break()
...
...
我预计此提案会被因其纯粹的丑陋而被拒绝。然而,它根本不需要更改语言语法,也不需要对现有 Python 程序进行任何更改。
实施
我从未研究过 Python 语言的实现本身,所以我不知道实现起来有多困难。如果此 PEP 被接受,但没有人有时间来实现该功能,我将尝试自己实现。
脚注
资源
这个问题以前出现过,但据我所知,从未解决过。
- comp.lang.python 上的“labeled breaks”,在
do...while循环的上下文中 - “break LABEL vs. exceptions + PROPOSAL”,在 python-list 上,与使用 Exceptions 进行流程控制相比
- python-list 上的“Named code blocks”,一个受需要带标签的 break / continue 的愿望驱动的建议
- mod_python bug fix 一个人在内部循环中设置一个标志的示例,该标志触发了包含循环中的 continue,以规避带标签的 break 和 continue 的缺失
版权
本文档已置于公共领域。
来源:https://github.com/python/peps/blob/main/peps/pep-3136.rst