乐趣区

关于python:Python-为什么会有个奇怪的对象

本文出自“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

退出移动版