2001:IPython 誕生與 Python 2.2 的新式類別、產生器#
事件#
2001 年發生了兩件影響深遠的事:
- IPython 誕生:哥倫比亞大學博士生 Fernando Pérez(物理學背景)在 2000–2001 年間開發了 IPython——一個加強版的互動式 Python shell,提供彩色輸出、Tab 補全、指令歷史、魔術指令(
%timeit等)。 - Python 2.2(2001 年 12 月):統一型別與類別(PEP 252/253,「新式類別」new-style classes),以及產生器
yield(PEP 255)。
為何重要#
- IPython 是科學 Python 生態的種子:沒有 IPython,就沒有 2011 年的 IPython Notebook,也就沒有 2014 年的 Jupyter——整個「互動式科學計算」傳統從這裡開始。
- 統一型別與類別是 Python 物件模型史上最重要的一次手術:修復了「內建型別(list、int)與使用者類別是兩個平行世界」的歷史缺陷。
- 產生器讓 Python 有了惰性求值(lazy evaluation)的迭代抽象,日後成為 2.4 生成器運算式、甚至 async/await 的技術基礎。
理論與實用原因#
- 理論原因(新式類別):在 2.2 之前,
int、list等內建型別無法被繼承——這違反 Smalltalk 以來「一切皆物件」的統一模型理論。2.2 引入 descriptors 協議(__get__/__set__),把屬性存取也統一成物件協議;統一後class MyList(list)才成為可能。 - 理論原因(產生器):源自 CLU(1975) 的 iterators 與 Icon 語言的 generators——「暫停/恢復函式執行」的 coroutine 理論。產生器把「實作迭代器」從「寫一個有狀態的類別(要實作
__iter__/__next__)」簡化為「一個含yield的函式」,狀態由直譯器自動保存。 - 實用原因(IPython):科學家需要 REPL 做「探索式計算」——即時看結果、即時畫圖;標準 Python shell 太陽春(無補全、無歷史搜尋)。
- 實用原因(產生器):處理大型檔案/資料流時,把整個列表放進記憶體不切實際;惰性逐項產出可以處理無限序列。
彌補了什麼缺陷#
- 新式類別彌補了「內建型別不可繼承、兩套物件模型不一致」的缺陷。
- 產生器彌補了「自訂迭代器樣板碼太多、串流處理必須整批載入記憶體」的缺陷。
- IPython 彌補了標準 shell「無補全、無內省、不適合科學探索」的缺陷。
程式範例#
2.2 之前 vs 之後:內建型別與使用者類別的統一
# 2.2 之前:內建型別不可繼承!
# class MyList(list): # TypeError: cannot inherit from built-in type
# 2.2 之後(新式類別):統一物件模型
class MyList(list):
def sum(self):
return sum(self)
ml = MyList([1, 2, 3])
ml.sum() # 6 —— 內建型別的行為 + 自訂方法
產生器 yield(PEP 255)——前後對比:
# 2.2 之前:自訂迭代器要寫有狀態的類別,樣板碼多
class Countdown:
def __init__(self, n): self.n = n
def __iter__(self): return self
def __next__(self):
if self.n <= 0: raise StopIteration
self.n -= 1
return self.n + 1
# 2.2 之後:一個含 yield 的函式,狀態由直譯器自動保存
def countdown(n):
while n > 0:
yield n # 暫停並回傳,下次從這裡恢復
n -= 1
for i in countdown(3): print(i) # 3 2 1
c = countdown(2)
next(c) # 2 —— 可處理無限/串流序列
IPython 的互動式探索(對比標準 shell):
In [1]: data = [1, 2, 3]
In [2]: data. # Tab 補全列出所有方法
In [3]: %timeit sum(data) # 魔術指令:量測執行時間
In [4]: data? # 內省:顯示物件的文件