本文出自“Python 为什么”系列,请查看全副文章
在写上一篇《Python 为什么要有 pass 语句?》时,我想到一种特地的写法,很多人会把它当成 pass 语句的代替。在文章公布后,果然有三条留言提及了它。
所谓特地的写法就是上面这个:
# 用 ... 代替 pass
def foo():
...
它是中文标点符号中的半个省略号,也即由英文的 3 个点组成。如果你是第一次看到,很可能会感觉奇怪:这玩意是怎么回事?(PS:如果你晓得它,认真看过本文后,你同样可能会感觉奇怪!)
1、认识一下“…”内置常量
事实上,它是 Python 3 中的一个内置对象,有个正式的名字叫作——Ellipsis,翻译成中文就是“省略号”。
更精确地说,它是一个内置常量(Built-in Constant),是 6 大内置常量之一(另外几个是 None、False、True、NotImplemented、__debug__)。
对于这个对象的根底性质,上面给出了一张截图,你们应该能明确我的意思:
“…“并不神秘,它只是一个可能不多见的符号型对象而已。用它替换 pass,在语法上并不会报错,因为 Python 容许一个对象不被赋值援用。
严格来说, 这是鸡鸣狗盗,在语义上站不住脚——把“…”或其它常量或已被赋值的变量放在一个空的缩进代码块中,它们是与动作无关的,只能表白出“这有个没用的对象,不必管它”。
Python 容许这些不被理论应用的对象存在,然而聪慧的 IDE 应该会有所提醒(我用的是 Pycharm),比方通知你:Statement seems to have no effect
。
然而“…”这个常量仿佛受到了非凡看待,我的 IDE 上没有作提醒。
很多人曾经习惯上把它当成 pass 那样的空操作来用了(在最早引入它的邮件组探讨中,就是举了这种用法的例子)。但我自己还是偏向于应用 pass,不晓得你是怎么想的呢?
2、奇怪的 Ellipsis 和 …
… 在 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 曾经被淘汰了。
3、为什么要应用“…”对象?
接下来,让咱们回到题目的问题:Python 为什么要应用“…”对象?
这里就只聚焦于 Python 3 的“…”了,不去追溯 Ellipsis 的历史和现状。
之所以会问这个问题,我的用意是想晓得: 它有什么用途,可能解决什么问题?从而窥探到 Python 语言设计中的更多细节。
大略有如下的几种答案:
(1)扩大切片语法
官网文档中给出了这样的阐明:
Special value used mostly in conjunction with extended slicing syntax for user-defined container data types.
这是个非凡的值,通常跟扩大的切片语法相结合,用在自定义的数据类型容器上。
文档中没有给出具体实现的例子,但用它联合__getitem__() 和 slice() 内置函数,能够实现相似于 [1, …, 7] 取出 7 个数字的切片片段的成果。
因为它次要用在数据操作上,可能大部分人很少接触。据说 Numpy 把它用在了一些语法糖用法上,如果你在用 Numpy 的话,能够摸索一下都有哪些玩法?
(2)表白“未实现的代码”语义
… 能够被用作占位符,也就是我在《Python 为什么要有 pass 语句?》中提到 pass 的作用。前文中对此已有局部剖析。
有人感觉这样很 cute,这种想法取得了 Python 之父 Guido 的反对:
(3)Type Hint 用法
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 来作动态代码查看。
(4)示意有限循环
最初,我认为有一个十分终极的起因,除了引入“…”来示意,没有更好的办法。
先看看两个例子:
两个例子的后果中都呈现了“…”,它示意的是什么货色呢?
对于列表和字典这样的容器,如果其外部元素是可变对象的话,则存储的是对可变对象的援用。那么,当其外部元素又援用容器本身时,就会递归地呈现有限循环援用。
有限循环是无奈穷尽地示意进去的,Python 中用 … 来示意,比拟形象易懂,除了它,恐怕没有更好的抉择。
最初,咱们来总结一下本文的内容:
- … 是 Python 3 中的一个内置常量,它是一个单例对象,尽管是 Python 2 中就有的 Ellipsis 的别称,但它的性质曾经跟旧对象各奔前程
- … 能够代替 pass 语句作为占位符应用,然而它作为一个常量对象,在占位符语义上并不谨严。很多人曾经在习惯上承受它了,无妨一用
- … 在 Python 中不少的应用场景,除了占位符用法,还能够反对扩大切片语法、丰盛 Type Hint 类型查看,以及示意容器对象的有限循环
- … 对大多数人来说,可能并不多见(有人还可能因为它是一种符号特例而排挤它),但它的存在,有些时候可能带来便当。心愿本文能让更多人意识它,那么文章的目标也就达成了~
如果你感觉本文剖析得不错,那你应该会喜爱这些文章:
1、Python 为什么应用缩进来划分代码块?
2、Python 的缩进是不是反人类的设计?
3、Python 为什么不必分号作语句终止符?
4、Python 为什么没有 main 函数?为什么我不举荐写 main 函数?
5、Python 为什么举荐蛇形命名法?
6、Python 为什么不反对 i++ 自增语法,不提供 ++ 操作符?
7、Python 为什么只需一条语句“a,b=b,a”,就能间接替换两个变量?
8、Python 为什么用 # 号作正文符?
9、Python 为什么要有 pass 语句?
本文属于“Python 为什么”系列(Python 猫出品),该系列次要关注 Python 的语法、设计和倒退等话题,以一个个“为什么”式的问题为切入点,试着展示 Python 的迷人魅力。所有文章将会归档在 Github 上,我的项目地址:https://github.com/chinesehuazhou/python-whydo