本文出自“Python为何”系列,请查看 所有文章
在写上一篇《Python 为何要有 pass 语句?》时,我想到一种特别的写法,不少人会把它当成 pass 语句的替代。在文章发布后,果真有三条留言说起了它。html
所谓特别的写法就是下面这个:python
# 用 ... 替代 pass def foo(): ...
它是中文标点符号中的半个省略号,也即由英文的 3 个点组成。若是你是第一次看到,极可能会以为奇怪:这玩意是怎么回事?(PS:若是你知道它,仔细看过本文后,你一样可能会以为奇怪!)android
事实上,它是 Python 3 中的一个内置对象,有个正式的名字叫做——Ellipsis,翻译成中文就是“省略号”。git
更准确地说,它是一个内置常量(Built-in Constant),是 6 大内置常量之一(另外几个是 None、False、True、NotImplemented、__debug__)。github
关于这个对象的基础性质,下面给出了一张截图,大家应该能明白个人意思:cookie
“...“并不神秘,它只是一个可能很少见的符号型对象而已。用它替换 pass,在语法上并不会报错,由于 Python 容许一个对象不被赋值引用。session
严格来讲, 这是旁门左道,在语义上站不住脚——把“...”或其它常量或已被赋值的变量放在一个空的缩进代码块中,它们是与动做无关的,只能表达出“这有个没用的对象,不用管它”。app
Python 容许这些不被实际使用的对象存在,然而聪明的 IDE 应该会有所提示(我用的是 Pycharm),好比告诉你:Statement seems to have no effect
。函数
可是“...”这个常量彷佛受到了特殊对待,个人 IDE 上没有做提示。工具
不少人已经习惯上把它当成 pass 那样的空操做来用了(在最先引入它的邮件组讨论中,就是举了这种用法的例子)。但我本人仍是倾向于使用 pass,不知道你是怎么想的呢?
... 在 PEP-3100 中被引入,最先合入在 Python 3.0 版本,而 Ellipsis 则在更早的版本中就已包含。
虽然官方说它们是同一个对象的两种写法,并且说成是单例的(singleton),但我还发现一个很是奇怪的现象,与文档的描述是冲突的:
如你所见,赋值给 ... 时会报错SyntaxError: cannot assign to Ellipsis
,然而 Ellipsis 却能够被赋值,它们的行为根本就不一样嘛!被赋值以后,Ellipsis 的内存地址以及类型属性都改变了,它成了一个“变量”,再也不是常量。
做为对比,给 True 或 None 之类的常量赋值时,会报错SyntaxError: cannot assign to XXX
,可是给 NotImplemented 常量赋值时不会报错。
众所周知,在 Python 2 中也能够给布尔对象(True/False)赋值,然而 Python 3 已经把它们改形成不可修改的。
因此有一种可能的解释:Ellipsis 和 NotImplemented 是 Python 2 时代的遗留产物,为了兼容性或者只是由于核心开发者遗漏了,因此它们在当前版本(3.8)中还能够被赋值修改。
... 出生在 Python 3 的时代,或许在未来会彻底取代 Ellipsis。目前二者共存,它们不一致的行为值得咱们注意。个人建议:只使用"..."吧,就当 Ellipsis 已经被淘汰了。
接下来,让咱们回到标题的问题:Python 为何要使用“...”对象?
这里就只聚焦于 Python 3 的“...”了,不去追溯 Ellipsis 的历史和现状。
之因此会问这个问题,个人意图是想知道:它有什么用处,可以解决什么问题?从而窥探到 Python 语言设计中的更多细节。
大概有以下的几种答案:
官方文档中给出了这样的说明:
Special value used mostly in conjunction with extended slicing syntax for user-defined container data types.这是个特殊的值,一般跟扩展的切片语法相结合,用在自定义的数据类型容器上。
文档中没有给出具体实现的例子,但用它结合__getitem__() 和 slice() 内置函数,能够实现相似于 [1, ..., 7] 取出 7 个数字的切片片断的效果。
因为它主要用在数据操做上,可能大部分人不多接触。据说 Numpy 把它用在了一些语法糖用法上,若是你在用 Numpy 的话,能够探索一下都有哪些玩法?
... 能够被用做占位符,也就是我在《Python 为何要有 pass 语句?》中提到 pass 的做用。前文中对此已有部分分析。
有人以为这样很 cute,这种想法得到了 Python 之父 Guido 的支持 :
Python 3.5 引入的 Type Hint 是“...”的主要使用场合。
它能够表示不定长的参数,好比Tuple[int, ...]
表示一个元组,其元素是 int 类型,但数量不限。
它还能够表示不肯定的变量类型,好比文档中给出的这个例子:
from typing import TypeVar, Generic T = TypeVar('T') def fun_1(x: T) -> T: ... # T here def fun_2(x: T) -> T: ... # and here could be different fun_1(1) # This is OK, T is inferred to be int fun_2('a') # This is also OK, now T is str
T 在函数定义时没法肯定,当函数被调用时,T 的实际类型才被肯定。
在 .pyi 格式的文件中,... 随处可见。这是一种存根文件(stub file),主要用于存放 Python 模块的类型提示信息,给 mypy、pytype 之类的类型检查工具 以及 IDE 来做静态代码检查。
最后,我认为有一个很是终极的缘由,除了引入“...”来表示,没有更好的方法。
先看看两个例子:
两个例子的结果中都出现了“...”,它表示的是什么东西呢?
对于列表和字典这样的容器,若是其内部元素是可变对象的话,则存储的是对可变对象的引用。那么,当其内部元素又引用容器自身时,就会递归地出现无限循环引用。
无限循环是没法穷尽地表示出来的,Python 中用 ... 来表示,比较形象易懂,除了它,恐怕没有更好的选择。
最后,咱们来总结一下本文的内容:
若是你以为本文分析得不错,那你应该会喜欢这些文章:
四、Python 为何没有 main 函数?为何我不推荐写 main 函数?
六、Python 为何不支持 i++ 自增语法,不提供 ++ 操做符?
七、Python 为何只需一条语句“a,b=b,a”,就能直接交换两个变量?
本文属于“Python为何”系列(Python猫出品),该系列主要关注 Python 的语法、设计和发展等话题,以一个个“为何”式的问题为切入点,试着展示 Python 的迷人魅力。全部文章将会归档在 Github 上,项目地址:https://github.com/chinesehuazhou/python-whydo