本文原创并首发于公众号【Python猫】,未经受权,请勿转载。html
原文地址:mp.weixin.qq.com/s/OypPwnJ2v…java
6 月 22 日,Python 之父 Guido 发了一条推特,说了 Python 的一则历史故事,他说 elif 是从 C 语言中偷过来的:python
elif 是“else if”的简写,用于条件判断。当只有两个分支时,咱们会写成“if...else...”,当出现更多分支时,咱们会写成以下格式:编程
if 判断条件1:
作事情1
elif 判断条件2:
作事情2
else:
作其它事
复制代码
简写而成的 elif 不只是减小了几个字符,并且因为单一而清晰的用途,它还不会给咱们带来理解或使用上的困惑。编程语言
可是,简写法并非主流,完整写法才是主流,C 语言中就是采用完整的写法:ide
if(判断条件1)
{
作事情1
}
else if(判断条件2)
{
作事情2
}
else
{
作其它事
}
复制代码
没错,C 语言使用的是全拼写法,可是在它的预处理/预编译语句中,还有一个 elif 指令,Guido 所说的“偷”,就是从这来的:oop
#if 常量表达式1
// 编译1
#elif 常量表达式2
// 编译2
#else
// 编译3
#endif
复制代码
Python 没有预编译,因此所谓的偷,跟预编译没有关系,只是在对比两种写法后,借用了更简洁的写法而已。ui
为何 C 语言不把两种写法统一块儿来呢?这我不得而知了,而 Guido 在两种写法中,选择了后一种非主流却更好用的写法。我想对他说,你“偷”得好啊!idea
实际上,留言区里的人也有同感,纷纷表示:不介意、很 okay、很是喜欢,还有人说“不是偷,而是收获(harvested)”、“不是偷,而是把它提高了一些高度”……spa
前不久,我写了一篇《聊聊 print 的前世此生》,print 这个词就是从 C 语言中借用来的。除此以外,若是有人仔细比较这两种语言的关键字和习惯命名,确定会发现很多相同的内容。
编程语言间有一些共享的元素,这很常见,创造一门语言并不意味着要原创每个词句,毕竟大部分思想是共通的,做为基础设施的词语更是如此。
那么,我忽然好奇了:创造一门编程语言时,何时该借用,何时该创造呢?
这个问题看起来可能没啥意义,由于终其一辈子,咱们多数人也不大可能会参与创造一门编程语言。
但我以为它仍是极有意义的,首先,提问精神值得确定,其次,它还提供了一种溯源、甄别、遴选、创造的体系性视角,我认为这是求知的正确思惟方式。
带着这个疑惑,我特别想要考察的是 Python 的 for 循环。
若是你有其它语言基础,就知道 “for 循环”一般指的是这样的三段式结构:
for ( init; condition; increment ){
statement(s);
}
// java
for(int x = 10; x < 20; x = x+1) {
System.out.print("value of x : " + x );
System.out.print("\n");
}
复制代码
这种 C 风格的写法是很初级的东西,很多语言都借用了。可是,它的写法实在繁琐,为了更方便地遍历集合中的元素,人们在 for 循环以外又引入了升级版的 foreach 循环:
// java
int[] a = {1,2,3};
for(int i : a){
System.out.print(i + ",");
}
// C#
int[] a = {1,2,3};
foreach(int i in a){
System.Console.WriteLine(i);
}
复制代码
Python 中也有 for 循环,可是,它借用有度,在设计上早早就有本身独到的考虑,它直接摒弃了三段式的 for 循环,而是采用相似 foreach 的一种写法:
for iterating_var in sequence:
statements(s)
# 例子
for i in range(3):
print(i)
for i in "hello":
print(i)
复制代码
从表面上看,Python 的 for 循环跟其它语言的 foreach 很类似,但实际上,它的工做原理却很不相同。
为何会有不一样呢?主要是由于 Python 的 for 语句用于可迭代对象上,而不只仅是用于集合或者普通的容器(虽然它们也是可迭代对象),而可迭代对象还可再细分出迭代器与生成器,这会形成最终结果的极大差别。
先看看两个例子:
# 例1,普通可迭代对象
x = [1, 2, 3]
for i in x:
print(i)
for i in x:
print(i)
# 例2,迭代器或生成器
y = iter([1, 2, 3])
# y = (i for i in [1,2,3])
for i in y:
print(i)
for i in y:
print(i)
复制代码
例 1 中,“1 2 3”会被打印两次,而在例 2 中,则只会打印一次。
普通可迭代对象只有 __iter__() 魔术方法,而不像迭代器同样拥有 __next__() 魔术方法,这意味着它没法实现 自遍历
过程,同时在通过 for 循环的 它遍历
后,也不会破坏原有的结构。(这两个是我创造的概念,详见《Python进阶:迭代器与迭代器切片》)。
可是,迭代器是一种匮乏的设计,具备单向损耗的特性,遍历一次后就会被破坏掉,不能重复利用。(关于迭代器的设计问题,这篇文章值得一看《当谈论迭代器时,我谈些什么?》)。
这代表了,Python 中 for 循环的使用场景很广阔,并且它还可能带来非纯结果,即重复执行一样的代码块,会出现不一样的结果。
这是否是跟别的语言很不一样了呢?相同的关键字,类似的循环思想与写法,可是,带来的影响却有差异。
关于 Python 的 for 循环,还有一个很独特的设计,即 for-else 结构:
x = [1, 2, 3]
for i in x:
print(i, end = " ")
else:
print("ok")
# 输出:1 2 3 ok
复制代码
本文开头提到了 if-else 结构,只有在不知足 if 条件时,才会执行到 else 部分,也就是说,若是 if 语句为真,那执行完它的语句块后,就会跳过 else 部分。
这是一种非此即彼的并行关系 ,直白地说是“若是...就...;不然就...” 。
可是,对于 for-else 结构,for 语句并非在作真值判断,它的程序体必然会执行(除非可迭代对象为空),执行后还会继续执行 else 部分。
因此,它是一种先此后彼的串行关系 ,翻译出来则是“对于...就...;而后...”。
这种结构确定不是从 C 语言中借用来的,至因而否为 Python 所首创,我不肯定(大几率是,姑且认为是吧),若是有知情的同窗,烦请告知。
那么,为何 Python 要加上这种设计呢,它有什么实际的用途么?
x = [1,2,3]
for i in x:
if i % 2 == 0:
print(i) # match
break
else:
print("mismatch")
复制代码
上例的 for 部分增长了一个判断以及 break,这个 break 不只会跳出 for 循环自己,还会跳过 else 部分。
上例的做用是查找偶数,若是找到则打印出来,若是 for 循环遍历完都找不到,则进入到 else 分支,打印“mismatch”的结果。
因此,其实 else 是 for 循环有没有正常遍历结束的标记,若是在循环后没有达到某种目标而跳出(break、return 或者 raise),就能够在 else 中作必要的补充(记录日志、抛出异常等等)。
这种设计并不算一个好的设计,由于 else 会带来误解(if-else 那种非此即彼的关系),并且它的最大用途须要结合 break 等跳出循环的操做,可是这层信息却非显而易见的。
在核心开发者的邮件列表里,就有很多争论点,2009 年的这封邮件梳理了你们的讨论(mail.python.org/pipermail/p…)。
其中,有开发者提议:
这封邮件一一列举了这些观点的提出缘由及改进想法,而后又一一地反驳了它们,最后的结论是保持 for-else 写法不变,也就是你们如今看到的实现方式。它的完整语义是:
execute the for-loop (or while-loop)
if you reach a `break`, jump to the end of the `for...else` block
else execute the `else` suite
复制代码
也就是说,else 对标的是“是否执行 break”,若是没有 break,则进入else。
可是,我并不承认这种作法,由于 break 是隐含条件,在直观上咱们只看到了 for-else,很容易产生 if-else 那样的联想。所以,我反而赞同把 else 改成 then,以消除误会。
这封邮件的反驳意见是,改为 then 会引入新的关键字,所以很差。
我认为这个说法有些牵强(从使用者的角度),还记得本文开头的内容么,elif 就是新引入的关键字啊,看看它如今是多受欢迎。
elif 属于那种初看不知何意,但知道后确定会记住的词,并且也不大可能拼写错误。为了这点简洁易拼写的好处,它就被引入成新的关键字了。
for-else 中的 else 属于那种初看觉得知道含义的词,但实际却表达着不一样意思(准确地说是,因为不知道隐含条件,而形成的误解),为了清晰语义的好处,我认为能够引入新的关键词 then 来替代 else。
不过,我转念一想,如今讨论这个已经没有意义了,毕竟时间已通过去了,那都是 10 年前的讨论了。
若是在 Python 创造之初,或者在 Python 3 大版本改动之初,这个讨论就被提出,那极可能 for-else 会被设计成 for-then ,then 会像引入 elif 关键词同样被引入。
若是是那样,说不定 Guido 某天心血来潮提及这则历史小故事,留言区又会出现一大片的赞同之声呢。
聊到这里,意犹未尽,但主题彷佛有点跑偏,咱们来稍微总结几个要点吧:
本文谈到的内容很微小,好像没有什么实际的帮助,不知道 elif 来源、不知道 for 循环的细节、不知道 for-else 的用途与争论,这些通通都不会形成语言使用上的障碍。
但我仍是那个观点:
阅读 Python 的历史,从中你能够看到设计者们对功能细节的打磨过程,最终你就明白了,Python 是如何一步一步地发展成今天的样子。
这在我看来挺有趣的,更加增进了我对于 Python 的了解,之后在编程到某些用法的时候,脑海里满满都是故事,它顿时也会变得立体生动起来。
若是你读后有所收获,或者产生了不一样想法,欢迎来知识星球与我互动交流。
公众号【Python猫】, 本号连载优质的系列文章,有喵星哲学猫系列、Python进阶系列、好书推荐系列、技术写做、优质英文推荐与翻译等等,欢迎关注哦。