Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 增强提案

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 个或更多个情况。

目前解决此问题的方案是使用调度表,根据切换变量的值查找要执行的实现方法(这可以调整为平均复杂度为 O(1),例如使用完美哈希表)。这对于需要在不同情况方法中进行复杂和冗长处理的状态机非常有效。对于每种情况只处理一到两条指令的那些状态机,它并不高效,例如

def handle_data(self, data):
    self.stack.append(data)

pickle.py 中实现的状态机就是一个很好的例子,它用于序列化 Python 对象。其他突出案例包括 XML SAX 解析器和 Internet 协议处理程序。

提出的解决方案

本 PEP 提出了两种不同的,但不一定是冲突的解决方案

  1. 为 Python 编译器和 VM 添加一项优化,该优化可以检测到上述 if-elif-else 结构,并为其生成特殊的操作码,这些操作码使用只读字典来存储跳转偏移量。
  2. 为 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 语义(通过减少 __cmp__ 调用的次数并在受优化影响的 if-elif-else 结构中添加 __hash__ 调用)。为了确保这一点,切换只能在使用“from __future__”样式标志或切换变量是内置的不可变类型之一时安全地实现:int,float,string,unicode 等(不是子类型,因为不清楚这些类型是否仍然是不可变的)

为了防止对跳转表字典进行后修改(这可能用于访问受保护的代码),跳转表必须是只读类型(例如只读字典)。

优化仅应用于具有最小案例数量 n 的 if-elif-else 结构(其中 n 是一个数字,该数字需要根据性能测试来定义)。

解决方案 2:在 Python 中添加 switch 语句

新语法

switch EXPR:
    case CONSTANT:
        SUITE
    case CONSTANT:
        SUITE
    ...
    else:
        SUITE

(模数缩进变化)

“else”部分是可选的。如果未给出 else 部分,并且未匹配任何已定义的情况,则不执行任何操作,并忽略 switch 语句。这与当前的 if 行为一致。如果用户想使用异常来表示这种情况,他们可以定义一个 else 分支,然后实现所需的 action。

请注意,常量不必全都是同一类型,但它们应该与切换变量的类型可比较。

实现

编译器必须将此编译成类似于此的字节码

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 语句一样)。每个情况都定义了一个完整的独立套件;就像在 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

最后修改时间:2023 年 9 月 9 日 17:39:29 GMT