PEP 3136 – 带标签的 break 和 continue
- 作者:
- Matt Chisholm <matt-python at theory.org>
- 状态:
- 已拒绝
- 类型:
- 标准跟踪
- 创建:
- 2007-06-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 中,并在看到异常时捕获异常并退出。
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
...
两次退出仍然很不优雅。此实现还依赖于内层 (b) 循环将 b 泄漏到外层 for 循环,虽然这明确支持,但对于新手来说既令人惊讶,在我看来也是违反直觉和不好的做法。
程序员还必须记住在 condition two 上放置两个退出,并且不要在第二个退出之前插入代码。一个概念上的动作,即在 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
语句中,该语句在满足退出条件时执行。退出语句的深度无关紧要。控制流程不会分散。不需要额外的变量、异常或重新检查或存储控制条件。没有风险会导致代码在内层 (b) 循环的末尾和在外部 (a) 循环中重新检查退出条件之前被无意地插入。这些是带标签的 break
和 continue
将为 Python 带来的好处。
这个 PEP 不是什么
此 PEP 不是建议在 Python 中添加 GOTO 的提案。GOTO 允许程序员跳转到任意代码块或行,通常会使控制流程更难跟踪。尽管 break
和 continue
(无论是否支持标签)都可以被认为是 GOTO 的一种类型,但它要受到更多限制。另一个 Python 结构 yield
也可以被认为是 GOTO 的一种形式——一个限制更少的形式。此 PEP 的目标是提出对现有控制流工具 break
和 continue
的扩展,使控制流程更易于理解,而不是更难。
带标签的 break
和 continue
不能将控制转移到另一个函数或方法。它们甚至不能将控制转移到当前作用域中的任意代码行。目前,它们只能影响循环的行为,并且与 GOTO 大不相同,并且受到更多限制。此扩展允许它们影响当前命名空间中的任何封闭循环,但它不会将其行为更改为 GOTO 的行为。
规范
在所有这些提议下,break
和 continue
本身将继续按当前方式工作,默认应用于最内层的循环。
提案 A - 显式标签
for 和 while 循环语法之后将是一个可选的 as
或 label
(上下文)关键字 [2],然后是一个标识符,它可以用来标识要退出的循环(或应该继续的循环)。
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 循环语法,不如让希望使用带标签的退出的程序员在希望从更深层的循环中退出或继续该循环时,显式地创建迭代器并将其分配给一个标识符。
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
的语法,不如在Iterator类型中添加.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):
b_iter.break() # same as plain old break
...
if condition_two(a,b):
a_iter.break()
...
...
我预计此提案将因其纯粹的丑陋而被拒绝。然而,它无需对语言语法进行任何更改,也不需要对现有的Python程序进行任何更改。
实现
我从未接触过Python语言的实现本身,因此我并不知道实现这一点的难度。如果此PEP被接受,但没有人有空编写该功能,我将尝试自己实现它。
脚注
资源
这个问题以前出现过,尽管据我所知,它从未得到解决。
- 带标签的break,在comp.lang.python上,在
do...while
循环的上下文中。 - break LABEL vs. exceptions + PROPOSAL,在python-list上,与使用异常进行流程控制相比。
- 命名代码块,在python-list上,一个受对带标签的break/continue的需求驱动的建议。
- mod_python bug fix,一个示例,其中有人在内部循环中设置了一个标志,该标志触发包含循环的continue,以解决没有带标签的break和continue的问题。
版权
本文件已置于公共领域。
来源:https://github.com/python/peps/blob/main/peps/pep-3136.rst
最后修改时间:2023-09-09 17:39:29 GMT