Python为何不支持switch语句原理详解


Posted in Python onOctober 21, 2020

在这篇文章里,我们会聊一聊为什么 Python 决定不支持 switch 语句。

为什么想要聊这个话题呢?

主要是因为 switch 在其它语言中太常见了,而 Python 却不支持,这样的独特性本身就值得关注,而回答这个问题,也能更加看清 Python 在程序设计上的理念,了解 Python 在语法设计中的决策过程。

本文除了会详细分析 PEP-275 和 PEP-3103,还会介绍到 Python 最新的发展动态(PEP-622),即可能要引入的模式匹配(pattern matching)语法,相信这个话题会开阔大家的眼界,从而对 switch 语法有更为全面的认识。

1、switch 是什么?

在开始正题之前,我们需要先聊聊 switch 是什么?

一般而言,switch 的语法格式如下:

switch(expression){
  case value1:
    // 语句
    break; // 可选
  case value2:
    // 语句
    break; // 可选
  default: // 可选
    // 语句
}

使用流程图来表示,大概是这样的:

Python为何不支持switch语句原理详解

它的用法不难理解:switch 语句的值满足哪一个 case 情况,就会执行对应的代码块,执行时遇到 break 就跳出,否则就继续执行下一个 case 分支;一般会在最后放一个 default 分支,作为兜底。

大多数语言都提供了 switch 语句或者极其相似的东西,例如,在 C/C++/Java /Go 等静态语言中,它们都支持 switch-case 结构;在 Ruby 中有类似的 case-when 结构,在 Shell 语言中,有相似的 case-in 结构,在 Perl 中,有 switch-case-else……

switch 语句的好处是支持“单条件多分支”的选择结构,相比 if-else 的二分选择结构,在某些时候会更为简洁清晰。

但是,在 Python 中,我们看不到 switch-case 或者相近的语法结构,这是为什么呢?

2、Python 为什么不支持 switch?

官方文档中有一篇 FAQ 包含了这个问题:Why isn't there a switch or case statement in Python?

Python为何不支持switch语句原理详解

该文档给出了几个建议,告诉了我们几个 switch/case 的替代方案:

  • 使用 if-elif-else 条件判断语句
  • 使用字典,将 case 值与调用的函数映射起来
  • 使用内置 getattr() 检索特定的对象调用方法

曾有人提出过一些提案(即 PEP-275 和 PEP-3103),想给 Python 引入 switch 语法,然而,对于“是否以及如何进行靶场测试”,大家没有达成一致的共识。

靶场测试,即 range test,指的是对武器弹药的技术性能作各种测试验证,与药物的临床试验一样,都是在最终产品交付前的一项关键性测试。

官方文档对于“为什么 Python 不引入 switch”的解释,实际上来源于 Python 之父 Guido van Rossum 在 PEP-3103 中的意见:

Python为何不支持switch语句原理详解

出处:https://www.python.org/dev/peps/pep-3103/

A quick poll during my keynote presentation at PyCon 2007 shows this proposal has no popular support. I therefore reject it.

我在 PyCon 2007 的主题演讲中做了一个快速的民意调查,结果表明这个提案没有得到广泛的支持。因此,我拒绝了它。

简而言之,PEP 提案有了,语法实现也有了雏形,但是核心开发者们似乎没有达成一致意见,最终导致提案流产了。

3、PEP-275 与 PEP-3103 说了什么?

PEP-3103 是在 2006 年提出的,PEP-275 则是在 2001 年提出的,它们的共同之处是提出了引入 switch 语句的某种必要性、分析了好几种备选的实现方案,然而,结局是都被拒绝了。

Python为何不支持switch语句原理详解

出处:https://www.python.org/dev/peps/pep-0275/

那么,我们就先来回顾一下核心开发者们都做出了哪些讨论,看一看如果 Python 要实现 switch 结构,会是怎么样子的?(PS:PEP 里还涉及其它内容,本文只摘取与 switch 直接相关的部分)

PEP-275 提出的语法结构如下:

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

其中 else 分支是可选的,如果没有它,并且前面的分支都不满足的话,就什么也不做。另外 case 值 constant 支持不同类型,因为 expr 表达式的类型是动态的。

PEP-275 还提出让 switch 不支持掉落(fall-through)行为,即每个 case 分支相互独立而完整,不用像 C 语言那样需要写 break。

该 PEP 还列举了一些其它的 issue:

  • 重用现有关键字,不引入“switch”和“case”
  • 使用新的关键字,避免与 C 的 switch 概念混淆
  • 支持单分支多值选择(例如:case 'a', 'b', 'c': ...)
  • 还有建议支持范围取值判断(例如:case 10..14: ...)

除了首选方案,该 PEP 还记录了几种风格各异的语法方案:

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

PEP-275 记录下了不少重要的思路和问题,为 PEP-3103 的出现做了很好的铺垫。

那么,我们再来看看由 Guido 编写的 PEP-3103 说了些什么吧。

它首先认可了 PEP-275 中的两个基础设定,例如,实现“隐式的 break”,不让 case 分支出现 fall-through 这种转移控制权的情况(其它语言似乎都要求显式地写 break);else 分支是可选的,复用 else 关键字,而不用引入“default”。

对于 PEP-275 提倡的那种风格,Guido 比较认可,但也认为它的问题是缩进层次太多,因此建议减少代码分支缩进的空格数,例如本来缩进 4 空格,改为缩进 2 空格。

PEP-3103 还列举了另外三种实现方案,分析了它们的差异以及问题,具体内容从略,这里只给大家看看它们的风格:

# case 分支不缩进
switch EXPR:
case EXPR:
  SUITE
case EXPR:
  SUITE
....
else:
  SUITE

# switch 语句后不加冒号
switch EXPR
case EXPR:
  SUITE
case EXPR:
  SUITE
....
else:
  SUITE

# 省略 case 关键字
switch EXPR:
  EXPR:
    SUITE
  EXPR:
    SUITE
  ...
  else:
    SUITE

在基础语法之外,Guido 花了很多篇幅来讨论扩展语法(Extended Syntax),即在一个 case 分支中实现匹配多个值的复杂情况:

case EXPR, EXPR, ...:

# Guido 优选的
case in EXPR_LIST:

case *EXPR:

case [*]EXPR, [*]EXPR, ...:

case *(EXPR, EXPR, ...):

他重点考虑到的问题包括:switch 中表达式的结果是元组或可迭代对象的情况、case 的值被看成元组解包的情况、在 case 分支作“*”星号操作……

接着,Guido 又用了非常非常多的篇幅来分析该如何实现 switch,其中讨论到的主要思路有:

  • 使用等价的 if-elif 链来定义 switch 语句(可能会做些优化)
  • 同上,另外所有表达式都必须是可哈希的(hashable)
  • 看作是预先计算的字典的分派(dispatch)

PEP 中这部分的内容非常多,因为在每个思路上,Guido 还考虑到了好几种实现路径,这导致了他在复杂分析后的结论是:It is too early to decide( 现在做决定为时尚早)。

阅读完 PEP-3103 后,我总体的感觉是:Guido 的思路非常发散、层次丰富,但是,缺少了他在面对其它问题时那“快刀斩乱麻”式的洞察力。

也就是说,在诸多的可能性方案中,他力求面面俱到,最终无法说服自己做出一个独裁的决定。阻力主要来自于他自己,而不是其他人。

不过,之所以会出现这种情况,也许跟他的预设立场有关:他似乎认为“Python is fine without a switch statement”,因此尽管写了很长的 PEP,但只是在把问题复杂化,把议题搁置起来。

最后,他在 PyCon 上做了一个小范围调查,借此“名正言顺”地拒绝了自己发起的 PEP,试图堵住众人的悠悠之口……

4、未来会有 switch 语句么?

归结起来,之所以 Python 没有 switch 语句,原因有:switch 的实现细节/功能点未经敲定、没有 switch 也挺好的、有其它不错的方法替代 switch、Guido 的小任性……

但是,我们还是要追问一句:未来会有 switch 语句么?或者类似的多分支选择结构?

为什么要有此一问呢?原因是有太多语言自带 switch 语句,而且也有很多人尝试编写提供 switch 功能的库(我记得在 PyCoder's Weekly 里曾见到过两次)。

我(Python猫)本人自始至终并不喜欢 switch,几乎可以肯定地说,Python 未来也不会有 switch,但是,它很可能会引入一个类似于 switch 且更为复杂的语法结构!

2020 年 6 月,PEP-622 被提出了,它建议引入在 Scala、Erlang 和 Rust 等语言中的模式匹配语法(pattern matching)。

Python为何不支持switch语句原理详解

截至 2020 年 10 月,该 PEP 已被分解成另外三个 PEP(634-636),目前都处于草案阶段。考虑到核心开发者们的参与情况以及话题讨论的情况,这些提案极有可能会在未来版本(比如正在开发中的 3.10)中实现。

以一个求平均数的函数为例,模式匹配语法可以实现成这样:

def average(*args):
  match args:
    case [x, y]:      # captures the two elements of a sequence
      return (x + y) / 2
    case [x]:       # captures the only element of a sequence
      return x
    case []:
      return 0
    case x:        # captures the entire sequence
      return sum(x) / len(x)

match-case 结构神似于 switch-case 结构,然而它基于模式(pattern)而非表达式(expression),因此有更多待考虑的细节问题,也有更为广阔的应用空间。

对此话题感兴趣的读者,建议去查阅这几个新的 PEP。

最后,让我们回到标题中的问题:Python 为什么不支持 switch 语句?

官方文档的 FAQ 对此问题有一个解答,告诉我们有几个不错的替代写法,同时也留下了一条线索:曾有 PEP 提议引入 switch,只是没有成功实现。

沿着这条线索,本文拆解了 PEP-275 和 PEP-3103 这两篇文档,带大家看到了 Python 社区里提出过的风格各异的 switch 方案,以及诸多的悬而未决的问题。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
Python学习笔记之if语句的使用示例
Oct 23 Python
python微信跳一跳系列之色块轮廓定位棋盘
Feb 26 Python
Python常见工厂函数用法示例
Mar 21 Python
Python实现求解括号匹配问题的方法
Apr 17 Python
解决python matplotlib imshow无法显示的问题
May 24 Python
python 列表中[ ]中冒号‘:’的作用
Apr 30 Python
对Pytorch中Tensor的各种池化操作解析
Jan 03 Python
python设置环境变量的作用整理
Feb 17 Python
Python基于Hypothesis测试库生成测试数据
Apr 29 Python
在Tensorflow中实现leakyRelu操作详解(高效)
Jun 30 Python
Ubuntu 20.04安装Pycharm2020.2及锁定到任务栏的问题(小白级操作)
Oct 29 Python
Python Pycharm虚拟下百度飞浆PaddleX安装报错问题及处理方法(亲测100%有效)
May 24 Python
基于Python爬取素材网站音频文件
Oct 21 #Python
ffmpeg+Python实现B站MP4格式音频与视频的合并示例代码
Oct 21 #Python
运行Python编写的程序方法实例
Oct 21 #Python
Python读写csv文件流程及异常解决
Oct 20 #Python
Python脚本打包成可执行文件过程解析
Oct 20 #Python
PyQt5的QWebEngineView使用示例
Oct 20 #Python
Python测试框架:pytest学习笔记
Oct 20 #Python
You might like
php 信息采集程序代码
2009/03/17 PHP
一组PHP加密解密函数分享
2014/06/05 PHP
Laravel SQL语句记录方式(推荐)
2016/05/26 PHP
详解PHP如何更好的利用PHPstorm的自动提示
2017/08/18 PHP
PHP校验15位和18位身份证号的类封装
2018/11/07 PHP
Thinkphp5 自定义上传文件名的实现方法
2019/07/23 PHP
javascript 特性检测并非浏览器检测
2010/01/15 Javascript
跨浏览器通用、可重用的选项卡tab切换js代码
2011/09/20 Javascript
JavaScript高级程序设计(第3版)学习笔记 概述
2012/10/11 Javascript
使用insertAfter()方法在现有元素后添加一个新元素
2014/05/28 Javascript
详解原生JavaScript实现jQuery中AJAX处理的方法
2016/05/10 Javascript
js实现当鼠标移到表格上时显示这一格全部内容的代码
2016/06/12 Javascript
JavaScript实现通过select标签跳转网页的方法
2016/09/29 Javascript
详解Vue2.X的路由管理记录之 钩子函数(切割流水线)
2017/05/02 Javascript
js中时间格式化的几种方法
2018/07/22 Javascript
详解如何搭建mpvue框架搭配vant组件库的小程序项目
2019/05/16 Javascript
微信小程序如何调用新闻接口实现列表循环
2019/07/02 Javascript
NodeJS 文件夹拷贝以及删除功能
2019/09/03 NodeJs
bootstrap+spring boot实现面包屑导航功能(前端代码)
2019/10/09 Javascript
express异步函数异常捕获示例详解
2020/11/30 Javascript
vue实现图片裁剪后上传
2020/12/16 Vue.js
python pdb调试方法分享
2014/01/21 Python
python进程管理工具supervisor的安装与使用教程
2017/09/05 Python
浅谈python 里面的单下划线与双下划线的区别
2017/12/01 Python
PyQt5实现无边框窗口的标题拖动和窗口缩放
2018/04/19 Python
Python中分支语句与循环语句实例详解
2018/09/13 Python
详解Python 爬取13个旅游城市,告诉你五一大家最爱去哪玩?
2019/05/07 Python
Python数据结构dict常用操作代码实例
2020/03/12 Python
CSS3对图片照片进行边缘模糊处理的实现
2018/08/08 HTML / CSS
探讨HTML5移动开发的几大特性(必看)
2015/12/30 HTML / CSS
《识字五》教学反思
2014/03/01 职场文书
企业道德讲堂实施方案
2014/03/19 职场文书
幼儿园大班家长评语
2014/04/17 职场文书
教师岗位职责范本
2015/04/02 职场文书
大学生村官驻村工作心得体会
2016/01/23 职场文书
解析mybatis-plus中的resultMap简单使用
2021/11/23 Java/Android