PEP 275 – 切换多个值
- 作者:
- Marc-André Lemburg <mal at lemburg.com>
- 状态:
- 已拒绝
- 类型:
- 标准跟踪
- 创建日期:
- 2001年11月10日
- Python 版本:
- 2.6
- 发布历史:
拒绝通知
一个针对Python 3000的类似PEP,PEP 3103,已经被拒绝,所以这个提案也没有被接受的机会。
摘要
本PEP提出了增强Python在处理单个变量具有多个可能值时的切换性能的策略。
问题
直到Python 2.5,编写多值切换的典型方法是使用以下类型的长切换结构:
if x == 'first state':
...
elif x == 'second state':
...
elif x == 'third state':
...
elif x == 'fourth state':
...
else:
# default handling
...
这对于短切换结构来说工作正常,因为重复加载局部变量(本例中的变量x)并将其与某个常量进行比较的开销很低(平均复杂度为O(n))。然而,当使用这样的结构来编写状态机(例如编写解析器所需的状态机)时,可能的州数很容易达到10个或更多。
目前解决这个问题的方法是使用一个调度表,根据切换变量的值来查找要执行的case实现方法(这可以通过完美哈希表等方式进行优化,使其平均复杂度为O(1))。这对于需要复杂而冗长处理的不同case方法的状态机来说效果很好。但对于每个case只处理一两条指令的状态机(例如)来说,性能不佳。
def handle_data(self, data):
self.stack.append(data)
一个很好的例子是pickle.py中实现的状态机,它用于序列化Python对象。其他突出的例子包括XML SAX解析器和互联网协议处理器。
提议的解决方案
本PEP提出了两种不同但不一定冲突的解决方案
- 为Python编译器和VM添加优化,检测上述if-elif-else结构并为其生成特殊的字节码,这些字节码使用只读字典存储跳转偏移量。
- 为Python添加新语法,模拟C风格的switch语句。
第一种解决方案的优点是不依赖于向语言添加新关键字,而第二种解决方案看起来更简洁。两者都涉及一些运行时开销,以确保切换变量是不可变的且可哈希的。
两种解决方案都使用字典查找来找到正确的跳转位置,因此它们在要求切换变量和常量都与字典实现兼容(可哈希、可比较、a==b => hash(a)==hash(b))方面共享相同的问题空间。
解决方案1:优化if-elif-else
实施
编译器应该能够检测到具有以下签名的if-elif-else结构
if x == 'first':...
elif x == 'second':...
else:...
即,左侧总是引用相同的变量,右侧是可哈希的不可变内置类型。右侧不必都是相同的类型,但它们应该与左侧切换变量的类型可比较。
然后,编译器可以设置一个只读(完美)哈希表,将其存储在常量中,并在标准if-elif-else字节码流前面添加一个操作码SWITCH,该操作码触发以下运行时行为
在运行时,SWITCH将检查x是否是众所周知的不可变类型(字符串、unicode、数字),并使用哈希表查找正确的操作码片段。如果不满足此条件,解释器应通过简单地跳过SWITCH操作码并继续执行通常的if-elif-else字节码流来恢复到标准的if-elif-else处理。
问题
新的优化不应改变当前的Python语义(通过减少受优化影响的if-elif-else结构中的`__cmp__`调用数量并添加`__hash__`调用)。为确保这一点,只有在使用“from __future__”样式标志时,或者切换变量是内置不可变类型之一:int、float、string、unicode等(而不是子类型,因为不清楚这些是否仍然不可变),才能安全地实现切换。
为防止对跳转表字典进行后修改(这可能用于访问受保护的代码),跳转表必须是只读类型(例如,只读字典)。
此优化仅应用于具有最少n个case的if-elif-else结构(其中n是一个尚未根据性能测试确定的数字)。
解决方案2:在Python中添加一个switch语句
新语法
switch EXPR:
case CONSTANT:
SUITE
case CONSTANT:
SUITE
...
else:
SUITE
(除去缩进变化)
“else”部分是可选的。如果没有给出else部分并且没有匹配的已定义case,则不执行任何操作,并忽略switch语句。这与当前的if行为一致。希望使用异常来指示这种情况的用户可以定义一个else分支,然后实现预期的操作。
请注意,常量不必都是相同的类型,但它们应该与切换变量的类型可比较。
实施
编译器必须将其编译成类似于这样的字节码
def whatis(x):
switch(x):
case 'one':
print '1'
case 'two':
print '2'
case 'three':
print '3'
else:
print "D'oh!"
转换为(省略POP_TOP和SET_LINENO)
6 LOAD_FAST 0 (x)
9 LOAD_CONST 1 (switch-table-1)
12 SWITCH 26 (to 38)
14 LOAD_CONST 2 ('1')
17 PRINT_ITEM
18 PRINT_NEWLINE
19 JUMP 43
22 LOAD_CONST 3 ('2')
25 PRINT_ITEM
26 PRINT_NEWLINE
27 JUMP 43
30 LOAD_CONST 4 ('3')
33 PRINT_ITEM
34 PRINT_NEWLINE
35 JUMP 43
38 LOAD_CONST 5 ("D'oh!")
41 PRINT_ITEM
42 PRINT_NEWLINE
>>43 LOAD_CONST 0 (None)
46 RETURN_VALUE
其中“SWITCH”操作码将根据“x”跳转到14、22、30或38。
Thomas Wouters已经编写了一个演示上述内容的补丁。您可以从[1]下载。
问题
switch语句不应实现穿透行为(C语言中的switch语句则会)。每个case定义一个完整且独立的套件;非常类似于if-elif-else语句。这还可以在循环内的switch语句中使用break。
如果解释器发现切换变量x不可哈希,它应该在运行时引发TypeError,指出问题。
还有其他语法提案,它们重用现有关键字并避免添加两个新关键字(“switch”和“case”)。另一些人认为关键字应该使用新术语,以避免与C语言中同名但语义略有不同(例如,不带break的穿透)的关键字混淆。一些提议的变体
case EXPR:
of CONSTANT:
SUITE
of CONSTANT:
SUITE
else:
SUITE
case EXPR:
if CONSTANT:
SUITE
if CONSTANT:
SUITE
else:
SUITE
when EXPR:
in CONSTANT_TUPLE:
SUITE
in CONSTANT_TUPLE:
SUITE
...
else:
SUITE
switch语句可以扩展为允许一个部分有多个值(例如,case 'a', 'b', 'c': ...)。另一个提议的扩展将允许值范围(例如,case 10..14: ...)。这些可能应该推迟,但在设计和实现第一个版本时已经考虑到。
示例
以下所有示例都使用解决方案2中提出的新语法。但是,所有这些示例也适用于解决方案1。
switch EXPR: switch x:
case CONSTANT: case "first":
SUITE print x
case CONSTANT: case "second":
SUITE x = x**2
... print x
else: else:
SUITE print "whoops!"
case EXPR: case x:
of CONSTANT: of "first":
SUITE print x
of CONSTANT: of "second":
SUITE print x**2
else: else:
SUITE print "whoops!"
case EXPR: case state:
if CONSTANT: if "first":
SUITE state = "second"
if CONSTANT: if "second":
SUITE state = "third"
else: else:
SUITE state = "first"
when EXPR: when state:
in CONSTANT_TUPLE: in ("first", "second"):
SUITE print state
in CONSTANT_TUPLE: state = next_state(state)
SUITE in ("seventh",):
... print "done"
else: break # out of loop!
SUITE else:
print "middle state"
state = next_state(state)
这是Jack Jansen发现的另一个不错的应用程序(基于参数类型进行切换)
switch type(x).__name__:
case 'int':
SUITE
case 'string':
SUITE
范围
XXX 解释“from __future__ import switch”
致谢
- Martin von Löwis(关于优化想法的问题)
- Thomas Wouters(switch语句+字节码编译器示例)
- Skip Montanaro(调度想法、示例)
- Donald Beaudry(switch语法)
- Greg Ewing(switch语法)
- Jack Jansen(类型切换示例)
参考资料
版权
本文档已置于公共领域。
来源:https://github.com/python/peps/blob/main/peps/pep-0275.rst