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