Following system colour scheme - Python 增强提案 Selected dark colour scheme - Python 增强提案 Selected light colour scheme - Python 增强提案

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

目前解决这个问题的方法是使用一个调度表,根据切换变量的值来查找要执行的case实现方法(这可以通过完美哈希表等方式进行优化,使其平均复杂度为O(1))。这对于需要复杂而冗长处理的不同case方法的状态机来说效果很好。但对于每个case只处理一两条指令的状态机(例如)来说,性能不佳。

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

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

提议的解决方案

本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语义(通过减少受优化影响的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

最后修改:2025-02-01 08:55:40 GMT