Python 2.0 有什麼新功能

作者:

A.M. Kuchling 和 Moshe Zadka

簡介

Python 的新版本 2.0 於 2000 年 10 月 16 日發布。本文介紹 2.0 中令人興奮的新功能、重點說明一些其他有用的變更,並指出一些可能需要重寫程式碼的不相容變更。

Python 的開發在各版本之間從未完全停止,錯誤修正和改進總是源源不斷地被提交。大量的小修正、一些最佳化、額外的說明字串和更好的錯誤訊息都納入了 2.0;要全部列出是不可能的,但它們確實很重要。如果你想看完整的清單,請查閱公開的 CVS 日誌。這些進展歸功於在 PythonLabs 工作的五位開發者現在有薪酬來專職修復錯誤,也歸功於遷移到 SourceForge 後改善的溝通。

Python 1.6 呢?

Python 1.6 可以被視為履行合約義務的 Python 版本。在核心開發團隊於 2000 年 5 月離開 CNRI 後,CNRI 要求建立一個 1.6 版本,包含所有在 CNRI 完成的 Python 工作。因此 Python 1.6 代表了 2000 年 5 月時 CVS 樹的狀態,最重要的新功能是 Unicode 支援。當然,開發在五月之後仍持續進行,所以 1.6 分支收到了一些修正以確保它與 Python 2.0 向前相容。因此 1.6 是 Python 演進的一部分,而非旁支。

那麼,你應該對 Python 1.6 很感興趣嗎?大概不需要。1.6 final 和 2.0 beta1 是在同一天(2000 年 9 月 5 日)釋出的,計畫是在大約一個月內完成 Python 2.0。如果你有應用程式需要維護,遷移到 1.6 導致程式出問題、修復它們、然後一個月內又因遷移到 2.0 而再次出問題,這似乎沒什麼意義;你最好直接升級到 2.0。本文描述的大多數真正有趣的功能只存在於 2.0,因為五月到九月之間完成了大量工作。

新的開發流程

Python 2.0 最重要的變更可能根本不在程式碼,而是 Python 的開發方式:2000 年 5 月,Python 開發者開始使用 SourceForge 提供的工具來儲存原始碼、追蹤錯誤報告和管理補丁提交佇列。要回報錯誤或為 Python 2.0 提交補丁,請使用 Python 專案頁面(位於 https://sourceforge.net/projects/python/)上的錯誤追蹤和補丁管理工具。

目前託管在 SourceForge 上最重要的服務是 Python CVS 樹,這是包含 Python 原始碼的版本控制儲存庫。以前大約只有 7 個人左右擁有 CVS 樹的寫入權限,所有補丁都必須由這份短名單上的人檢查並提交。這顯然不太具有擴展性。將 CVS 樹移至 SourceForge 後,可以授予更多人寫入權限;截至 2000 年 9 月,有 27 人能夠提交變更,增加了四倍。這讓我們有機會進行大規模變更,如果這些變更必須經過少數核心開發者過濾,就不會有人嘗試。例如有一天 Peter Schneider-Kamp 突發奇想,決定放棄 K&R C 相容性並將 Python 的 C 原始碼轉換為 ANSI C。在 python-dev 郵件列表上獲得批准後,他開始了持續約一週的密集提交,其他開發者也加入幫忙,工作就完成了。如果只有 5 個人有寫入權限,這項任務可能會被視為「不錯,但不值得花費所需的時間和精力」,永遠不會完成。

轉向使用 SourceForge 的服務使開發速度顯著提升。補丁現在會被提交、評論、由原作者以外的人修改,並在人們之間來回傳遞,直到補丁被認為值得簽入。程式錯誤 (bugs) 在一個集中的位置被追蹤,可以指派給特定的人修復,我們可以計算未解決的錯誤數量來衡量進度。這並非沒有代價:開發者現在有更多電子郵件要處理、更多郵件列表要關注,還必須為新環境編寫特殊工具。例如 SourceForge 發送的預設補丁和錯誤通知電子郵件完全沒有幫助,所以 Ka-Ping Yee 寫了一個 HTML 爬蟲來發送更有用的訊息。

新增程式碼的便利性造成了一些初期的成長陣痛,例如程式碼在還沒準備好或者還沒有得到開發者群組的明確同意之前就被簽入。已經形成的審批流程與 Apache 組織使用的流程有些類似。開發者可以對補丁投票 +1、+0、-0 或 -1;+1 和 -1 表示接受或拒絕,而 +0 和 -0 表示開發者對該變更基本上持中立態度,但略微傾向正面或負面。與 Apache 模式最顯著的不同是,投票本質上只是諮詢性質的,讓擁有「終身仁慈獨裁者」地位的 Guido van Rossum 了解一般意見。即使社群不同意他的意見,他仍然可以忽略投票結果來批准或拒絕一個變更。

製作實際的補丁是新增功能的最後一步,與早期提出好設計的任務相比通常比較容易。關於新功能的討論往往會爆發成冗長的郵件列表討論串,使討論難以追蹤,而且沒有人能讀完 python-dev 上的每一篇文章。因此,我們以網際網路 RFC 流程為藍本建立了一個相對正式的流程來撰寫 Python 增強提案(PEP)。PEP 是描述提議新功能的草案文件,並持續修改直到社群達成共識以接受或拒絕該提案。引用 PEP 1「PEP 目的與準則」的介紹:

PEP 代表 Python Enhancement Proposal(Python 增強提案)。PEP 是一份設計文件,向 Python 社群提供資訊,或描述 Python 的新功能。PEP 應該提供該功能的簡潔技術規格和該功能的基本原理。

我們打算讓 PEP 成為提議新功能、收集社群對議題意見、以及記錄 Python 設計決策的主要機制。PEP 作者負責在社群內建立共識並記錄不同意見。

閱讀 PEP 1 的其餘部分以了解 PEP 編輯流程、風格和格式的詳細資訊。PEP 保存在 SourceForge 上的 Python CVS 樹中,雖然它們不是 Python 2.0 發布板的一部分,但也可以從 https://peps.python.org/ 取得 HTML 格式。截至 2000 年 9 月,共有 25 個 PEP,從 PEP 201 "Lockstep Iteration" 到 PEP 225 "Elementwise/Objectwise Operators"。

Unicode

Python 2.0 中最大的新功能是一個新的基本資料型別:Unicode 字串。Unicode 使用 16 位元數字來表示字元,而不是 ASCII 使用的 8 位元數字,這意味著可以支援 65,536 個不同的字元。

Unicode 支援的最終介面是經過 python-dev 郵件列表上無數次激烈討論後確定的,主要由 Marc-André Lemburg 實作,基於 Fredrik Lundh 的 Unicode 字串型別實作。介面的詳細說明寫在 PEP 100 "Python Unicode Integration" 中。本文僅介紹 Unicode 介面最重要的幾點。

在 Python 原始碼中,Unicode 字串寫成 u"string"。可以使用新的跳脫序列 \uHHHH 來寫入任意 Unicode 字元,其中 HHHH 是從 0000 到 FFFF 的 4 位十六進位數字。也可以使用現有的 \xHH 跳脫序列,而八進位跳脫可用於 U+01FF 以下的字元,以 \777 表示。

Unicode 字串就像一般字串是不可變的序列型別。它們可以被索引和切片,但不能原地 (in place) 修改。Unicode 字串有一個 encode( [encoding] ) 方法,會回傳所需編碼的 8 位元字串。編碼以字串命名,如 'ascii''utf-8''iso-8859-1' 等。有定義了一個編解碼器 API 來實作和註冊新的編碼,然後就可在整個 Python 程式中使用。如果未指定編碼,預設編碼通常是 7 位元 ASCII,不過可以透過在自訂版本的 site.py 中呼叫 sys.setdefaultencoding(encoding) 函式來為你的 Python 安裝改變它。

結合 8 位元字串和 Unicode 字串時,總是會使用預設的 ASCII 編碼強制轉換為 Unicode;'a' + u'bc' 的結果是 u'abc'

新增了內建函式,並修改了現有的內建函式以支援 Unicode:

  • unichr(ch) 會回傳一個長度為 1 的 Unicode 字串,其包含字元 ch

  • ord(u),其中 u 是長度為 1 的一般字串或 Unicode 字串,以整數回傳該字元的數值。

  • unicode(string [, encoding]  [, errors] ) 從 8 位元字串建立 Unicode 字串。encoding 是指定要使用的編碼的字串。errors 參數指定如何處理目前編碼的無效字元;傳入 'strict' 作為值會在任何編碼錯誤時引發例外,而 'ignore' 會默默地忽略錯誤,'replace' 則在出現任何問題時使用 U+FFFD(官方替換字元)。

  • exec 陳述式以及各種內建函式如 eval()getattr()setattr() 現在也接受 Unicode 字串和一般字串。(在修復過程中可能遺漏了一些內建函式;如果你發現某個接受字串的內建函式完全不接受 Unicode 字串,請當作程式錯誤回報。)

新模組 unicodedata 提供了 Unicode 字元屬性的介面。例如,unicodedata.category(u'A') 回傳 2 字元的字串 'Lu','L' 表示它是字母,'u' 表示它是大寫。unicodedata.bidirectional(u'\u0660') 回傳 'AN',表示 U+0660 是阿拉伯數字。

codecs 模組包含查找現有編碼和註冊新編碼的函式。除非你想實作新的編碼,否則最常使用的是 codecs.lookup(encoding) 函式,它回傳一個 4 元素的元組:(encode_func, decode_func, stream_reader, stream_writer)

  • encode_func 是一個接受 Unicode 字串並回傳 2 元組 (string, length) 的函式。string 是一個 8 位元字串,包含轉換為給定編碼的 Unicode 字串的一部分(可能是全部),length 告訴你有多少 Unicode 字串被轉換了。

  • decode_funcencode_func 的相反操作,接受一個 8 位元字串並回傳一個 2 元組 (ustring, length),包含結果 Unicode 字串 ustring 和整數 length,告訴你消耗了多少 8 位元字串。

  • stream_reader 是一個支援從串流解碼輸入的類別。stream_reader(file_obj) 回傳一個支援 read()readline()readlines() 方法的物件。這些方法都會從給定的編碼進行轉換並回傳 Unicode 字串。

  • stream_writer 同樣是一個支援將輸出編碼到串流的類別。stream_writer(file_obj) 回傳一個支援 write()writelines() 方法的物件。這些方法預期接收 Unicode 字串,並在輸出時將其轉換為給定的編碼。

例如,以下程式碼會將 Unicode 字串寫入檔案並編碼為 UTF-8:

import codecs

unistr = u'\u0660\u2000ab ...'

(UTF8_encode, UTF8_decode,
 UTF8_streamreader, UTF8_streamwriter) = codecs.lookup('UTF-8')

output = UTF8_streamwriter( open( '/tmp/output', 'wb') )
output.write( unistr )
output.close()

以下程式碼接著從檔案讀取 UTF-8 輸入:

input = UTF8_streamreader( open( '/tmp/output', 'rb') )
print repr(input.read())
input.close()

支援 Unicode 的正規表示式可透過 re 模組使用,該模組有一個新的底層實作,稱為 SRE,由 Secret Labs AB 的 Fredrik Lundh 編寫。

新增了 -U 命令列選項,使 Python 編譯器將所有字串字面值解釋為 Unicode 字串字面值。這是用於測試和讓你的 Python 程式碼面向未來,因為 Python 的某個未來版本可能會放棄對 8 位元字串的支援,只提供 Unicode 字串。

串列綜合運算(List Comprehension)

串列是 Python 中的主力資料型別,許多程式在某個時候都會操作串列。對串列的兩個常見操作是對它們進行迴圈,然後選出符合特定條件的元素,或對每個元素套用某個函式。例如,給定一個字串串列,你可能想要取出所有包含特定子字串的字串,或去除每行末尾的空白。

現有的 map()filter() 函式可以用於此目的,但它們需要一個函式作為引數之一。如果有一個現成的內建函式可以直接傳遞就沒問題,但如果沒有的話你必須建立一個小函式來完成所需的工作,而且如果這個小函式需要額外的資訊,Python 的作用域規則會讓結果變得醜陋。以前一段的第一個例子為例,找出串列中所有包含給定子字串的字串。你可以這樣寫來完成它:

# 給定串列 L,建立一個包含子字串 S 的
# 所有字串的串列。
sublist = filter( lambda s, substring=S:
                     string.find(s, substring) != -1,
                  L)

由於 Python 的作用域規則,預設引數會被使用,以便 lambda 運算式建立的匿名函式知道要搜尋的是什麼子字串。串列綜合運算使這更簡潔:

sublist = [ s for s in L if string.find(s, S) != -1 ]

串列綜合運算的形式如下:

[ expression for expr in sequence1
             for expr2 in sequence2 ...
             for exprN in sequenceN
             if condition ]

for...in 子句包含要疊代的序列。這些序列不必具有相同的長度,因為它們不是並行疊代的,而是從左到右;這在以下段落中會更清楚地解釋。產生的串列的元素將是 expression 的連續值。最後的 if 子句是可選的;如果存在,只有當 condition 為真時,expression 才會被計算並加入結果。

為了讓語義非常清楚,串列綜合運算等價於以下 Python 程式碼:

for expr1 in sequence1:
    for expr2 in sequence2:
    ...
        for exprN in sequenceN:
             if (condition):
                  # 將運算式的值
                  # 附加到結果串列。

這意味著當有多個 for...in 子句時,結果串列的長度將等於所有序列長度的乘積。如果你有兩個長度為 3 的串列,輸出串列將有 9 個元素:

seq1 = 'abc'
seq2 = (1,2,3)
>>> [ (x,y) for x in seq1 for y in seq2]
[('a', 1), ('a', 2), ('a', 3), ('b', 1), ('b', 2), ('b', 3), ('c', 1),
('c', 2), ('c', 3)]

為了避免在 Python 的文法中引入歧義,如果 expression 是建立一個元組,它必須用括號包圍。下面第一個串列綜合運算是語法錯誤,而第二個是正確的:

# 語法錯誤
[ x,y for x in seq1 for y in seq2]
# 正確
[ (x,y) for x in seq1 for y in seq2]

串列綜合運算的概念最初來自函式程式設計語言 Haskell (https://www.haskell.org)。Greg Ewing 最有效地主張將它們加入 Python,並撰寫了最初的串列綜合運算補丁,然後在 python-dev 郵件列表上討論了好久好久,後來由 Skip Montanaro 持續更新。

擴增賦值(Augmented Assignment)

擴增賦值運算子是另一個長期被要求的功能,已加入 Python 2.0。擴增賦值運算子包括 +=-=*= 等等。例如陳述式 a += 2 將變數 a 的值增加 2,等同於稍長的 a = a + 2

支援的賦值運算子完整清單是 +=-=*=/=%=**=&=|=^=>>=<<=。Python 類別可以透過定義名為 __iadd__()__isub__() 等方法來覆寫擴增賦值運算子。例如,以下的 Number 類別儲存一個數字,並支援使用 += 來建立一個具有增加值的新實例。

class Number:
    def __init__(self, value):
        self.value = value
    def __iadd__(self, increment):
        return Number( self.value + increment)

n = Number(5)
n += 3
print n.value

__iadd__() 特殊方法會以增量值被呼叫,並應回傳一個具有適當修改值的新實例;這個回傳值會被繫結為左側變數的新值。

擴增賦值運算子最初是在 C 程式語言中引入的,大多數源自 C 的語言,如 awk、C++、Java、Perl 和 PHP 也支援它們。擴增指定的補丁由 Thomas Wouters 實作。

字串方法

到目前為止,字串操作功能都在 string 模組中,它通常是用 C 編寫的 strop 模組的前端。Unicode 的加入給 strop 模組帶來了困難,因為所有函式都需要為了接受 8 位元或 Unicode 字串而重寫。對於像 string.replace() 這樣接受 3 個字串引數的函式,這代表要處理八種可能的排列組合以及相應複雜的程式碼。

不過 Python 2.0 將問題推給字串型別,使字串操作功能可透過 8 位元字串和 Unicode 字串的方法使用。

>>> 'andrew'.capitalize()
'Andrew'
>>> 'hostname'.replace('os', 'linux')
'hlinuxtname'
>>> 'moshe'.find('sh')
2

有一件事沒有改變,儘管有一個值得注意的愚人節玩笑,那就是 Python 字串是不可變的。因此,字串方法會回傳新字串,而不會修改它們所操作的字串。

舊的 string 模組為了向後相容仍然保留,但它主要充當新字串方法的前端。

有兩個方法在 2.0 之前的版本中沒有對應功能:startswith()endswith(),不過它們在 JPython 中已經存在了一段時間。s.startswith(t) 等同於 s[:len(t)] == t,而 s.endswith(t) 等同於 s[-len(t):] == t

另一個值得特別提及的方法是 join()。字串的 join() 方法接收一個參數,即字串序列,等同於舊 string 模組中的 string.join() 函式,但引數順序相反。換句話說,s.join(seq) 等同於舊的 string.join(seq, s)

循環的垃圾回收

Python 的 C 實作使用參照計數來實作垃圾回收。每個 Python 物件都維護一個指向自身的參照數量計數,並在建立或銷毀參照時調整該計數。一旦參照計數達到零,該物件就不再可存取,因為你需要有物件的參照才能存取它,如果計數為零,就表示不再有任何參照存在。

參照計數有一些不錯的特性:它易於理解和實作,而且產生的實作具有可攜性、相當快速,並且能與其他有實作自己的記憶體處理方案的函式庫良好配合。參照計數的主要問題是它有時無法意識到物件已不再可存取,導致記憶體洩漏。這種情況發生在存在參照循環時。

考慮最簡單的循環情況,一個類別實例具有對自身的參照:

instance = SomeClass()
instance.myself = instance

執行上述兩行程式碼後,instance 的參照計數為 2;一個參照來自名為 'instance' 的變數,另一個來自實例的 myself 屬性。

如果下一行程式碼是 del instance,會發生什麼事?instance 的參照計數減少 1,所以它的參照計數變成 1;myself 屬性中的參照仍然存在。然而該實例已經無法透過 Python 程式碼存取,它可以被刪除。如果多個物件互相參照,它們就可以形成循環,導致所有這些物件都被洩漏。

Python 2.0 透過定期執行循環偵測演算法來修復這個問題,該演算法會尋找不可存取的循環並刪除涉及的物件。新的 gc 模組提供了執行垃圾回收、取得偵錯統計資訊和調整回收器參數的函式。

執行循環偵測演算法需要一些時間,因此會產生一些額外的開銷。希望在使用 2.0 得到循環回收的經驗後,Python 2.1 將能夠仔細調整以最小化這些開銷。目前因為基準測試很棘手所以還不清楚會損失多少效能,而且關鍵取決於程式建立和銷毀物件的頻率。如果即便是微小的速度損失你也都無法承受,或者懷疑循環回收機制有誤,可以在編譯 Python 時停用循環偵測,方式為執行 configure 腳本時指定 --without-cycle-gc 開關。

多人處理了這個問題並為解決方案做出了貢獻。循環偵測方法的早期實作由 Toby Kelsey 編寫。目前的演算法是 Eric Tiedemann 在訪問 CNRI 時建議的,Guido van Rossum 和 Neil Schemenauer 編寫了兩個不同的實作,後來由 Neil 整合起來。也有許多人在過程中提供了建議;python-dev 郵件列表的 2000 年 3 月存檔包含了大部分相關討論,特別是標題為 "Reference cycle collection for Python" 和 "Finalization again" 的討論串。

其他核心變更

對 Python 的語法和內建函式進行了各種小幅變更。這些變更的影響不大,但它們是便利的功能。

次要語言變更

新的語法使得使用引數元組和/或關鍵字引數字典呼叫給定函式更加方便。在 Python 1.5 及更早版本中,你會使用 apply() 內建函式:apply(f, args, kw) 使用引數元組 args 和字典 kw 中的關鍵字引數來呼叫函式 f()apply() 在 2.0 中仍然相同,但多虧了 Greg Ewing 的補丁,f(*args, **kw) 是達成相同效果的更簡短、更清晰的方式。此語法與定義函式的語法對稱:

def f(*args, **kw):
    # args 是位置引數的元組,
    # kw 是關鍵字引數的字典
    ...

現在可以透過在 print 後面加上 >> file 來將 print 陳述式的輸出導向至類檔案物件,類似於 Unix shell 中的重新導向運算子。以前你必須使用類檔案物件的 write() 方法,這缺乏 print 的便利性和簡潔性,或者你可以為 sys.stdout 指定新值然後恢復舊值。對於將輸出發送到標準錯誤,這樣寫簡單多了:

print >> sys.stderr, "Warning: action field not supplied"

現在可以在引入模組時重新命名它們,使用語法 import module as namefrom module import name as othername。該補丁由 Thomas Wouters 提交。

使用 % 運算子時新增了一個新的格式樣式;'%r' 將插入其引數的 repr()。這也是出於對稱性考量而新增的,這次是與現有的 '%s' 格式樣式對稱,後者會插入其引數的 str()。例如,'%r %s' % ('abc', 'abc') 回傳一個包含 'abc' abc 的字串。

以前沒有辦法實作一個覆寫 Python 內建 in 運算子並實作自訂版本的類別。如果 obj 存在於序列 seq 中,obj in seq 回傳 true;Python 透過簡單地嘗試序列的每個索引來計算這個,直到找到 obj 或遇到 IndexError。Moshe Zadka 貢獻了一個補丁,新增了一個 __contains__() 魔術方法,用於提供 in 的自訂實作。此外,用 C 編寫的新內建物件可以透過序列協定中的新插槽來定義 in 對它們的意義。

早期版本的 Python 使用遞迴演算法來刪除物件。深度巢狀的資料結構可能會導致直譯器填滿 C 堆疊並當機;Christian Tismer 重寫了刪除邏輯來修復這個問題。比較遞迴物件會造成無限遞迴並導致崩潰 (crash);Jeremy Hylton 重寫了程式碼使其不再崩潰,而是產生有用的結果。例如在這段程式碼之後:

a = []
b = []
a.append(a)
b.append(b)

a==b 的比較會回傳 true,因為這兩個遞迴資料結構是同構的 (isomorphic)。請參閱 python-dev 郵件列表 2000 年 4 月存檔中的 "trashcan and PR#7" 討論串,以了解最後決定此實作的討論和一些有用的相關連結。請注意,比較現在也可以引發例外。在早期版本的 Python 中,像 cmp(a,b) 這樣的比較操作總是會產生答案,即使使用者定義的 __cmp__() 方法遇到錯誤,因為產生的例外會被默默地吞掉。

已經完成了將 Python 移植到 Itanium 處理器上的 64 位元 Windows 的工作,主要由 ActiveState 的 Trent Mick 完成。(令人困惑的是,sys.platform 在 Win64 上仍然是 'win32',因為似乎為了方便移植,MS Visual C++ 在 Itanium 上將程式碼視為 32 位元。)PythonWin 也支援 Windows CE;詳見 https://pythonce.sourceforge.net/ 上的 Python CE 頁面以取得更多資訊。

另一個新平台是 Darwin/MacOS X;Python 2.0 中包含了對它的初步支援。如果你指定 "configure --with-dyld --with-suffix=.x",動態載入就可以運作。請參閱 Python 原始碼發布板中的 README 以取得更多指示。

程式碼中的區域變數在被指定值前就被參照的話,經常引發令人困惑的 NameError 例外,這個 Python 的缺陷有被嘗試改善。拿以下程式碼為例,它於 1.5.2 和 2.0 中都會在 print 陳述式上引發例外;在 1.5.2 中會引發 NameError 例外,而 2.0 會引發新的 UnboundLocalError 例外。UnboundLocalErrorNameError 的子類別,所以任何預期會引發 NameError 的現有程式碼應該仍然可以運作。

def f():
    print "i=",i
    i = i + 1
f()

引入了兩個新例外,TabErrorIndentationError。它們都是 SyntaxError 的子類別,當發現 Python 程式碼縮排不正確時就會被引發。

內建函式的變更

新增了一個內建函式 zip(seq1, seq2, ...)zip() 回傳一個元組串列,其中每個元組包含來自每個引數序列的第 i 個元素。zip()map(None, seq1, seq2) 之間的區別是,如果序列的長度不同,map() 會用 None 填充序列,而 zip() 會將回傳的串列截斷為最短引數序列的長度。

當第一個引數是字串時,int()long() 函式現在接受可選的 "base" 參數。int('123', 10) 回傳 123,而 int('123', 16) 回傳 291。int(123, 16) 會引發 TypeError 例外,訊息為 "can't convert non-string with explicit base"。

sys 模組中新增了一個包含更詳細版本資訊的新變數。sys.version_info 是一個元組 (major, minor, micro, level, serial)。例如,在假設的 2.0.1beta1 中,sys.version_info 會是 (2, 0, 1, 'beta', 1)level 是一個字串,如 "alpha""beta" 或最終版本的 "final"

字典有一個奇特的新方法 setdefault(key, default),其行為類似於現有的 get() 方法。然而如果鍵不存在,setdefault() 既會像 get() 一樣回傳 default 的值,也會將其作為 key 的值插入字典。因此,以下幾行程式碼:

if dict.has_key( key ): return dict[key]
else:
    dict[key] = []
    return dict[key]

可以簡化為單一的 return dict.setdefault(key, []) 陳述式。

直譯器會設定最大遞迴深度,以便在填滿 C 堆疊並導致核心傾印 (core dump) 或 GPF 之前捕獲失控的遞迴。以前這個限制在編譯 Python 時就固定了,但在 2.0 中最大遞迴深度可以使用 sys.getrecursionlimit()sys.setrecursionlimit() 來讀取和修改。預設值是 1000,可以透過執行新的腳本 Misc/find_recursionlimit.py 來找到給定平台約略的最大值。

移植到 2.0

新的 Python 版本努力與先前的版本相容,而且過去記錄維持很好。然而有些變更被認為只是足夠有用而已,通常是因為它們修正了後來證明是完全錯誤的初始設計決策,因此並不總是能避免破壞向後相容性。本節列出了 Python 2.0 中可能導致舊 Python 程式碼出問題的變更。

可能會破壞最多程式碼的變更是收緊某些方法接受的引數。某些方法會接受多個引數並將它們視為元組,特別是各種串列方法如 append()insert()。在早期版本的 Python 中,如果 L 是一個串列,L.append( 1,2 ) 會將元組 (1,2) 附加到串列中。在 Python 2.0 中,這會導致引發 TypeError 例外,訊息為:'append requires exactly 1 argument; 2 given'。修正方法單純是加一組額外的括號來將兩個值作為元組傳遞:L.append( (1,2) )

這些方法的早期版本更寬容,因為它們使用 Python C 介面中的舊函式來剖析引數;2.0 將它們現代化為使用 PyArg_ParseTuple(),即目前的引數剖析函式,它提供更有幫助的錯誤訊息並將多引數呼叫視為錯誤。如果你必須使用 2.0 但無法修正你的程式碼,你可以編輯 Objects/listobject.c 並定義前處理器 (preprocessor) 符號 NO_STRICT_LIST_APPEND 來保留舊行為;但不建議這樣做。

socket 模組中的某些函式在這方面仍然很寬容。例如 socket.connect( ('hostname', 25) ) 仍是傳遞一個代表 IP 位址的元組的正確形式,但 socket.connect('hostname', 25) 也可以運作。socket.connect_exsocket.bind 同樣寬鬆。2.0alpha1 收緊了這些函式,但因為文件實際上使用了錯誤的多引數形式,許多人寫的程式碼會因更嚴格的檢查而出問題。GvR 考慮公眾反應而撤回了變更,所以 socket 模組文件因此被修正了,多引數形式只是被標記為已棄用;它將會在未來的 Python 版本中再次被收緊。

字串字面值中的 \x 跳脫現在正好取 2 個十六進位數字。以前它會消耗 'x' 後面的所有十六進位數字並取結果的最低 8 位元,所以 \x123456 等同於 \x56

AttributeErrorNameError 例外有了更友善的錯誤訊息,其文字會類似於 'Spam' instance has no attribute 'eggs'name 'eggs' is not defined。以前錯誤訊息只是缺少的屬性名稱 eggs,利用這一事實編寫的程式碼在 2.0 中會出問題。

已經完成了一些使整數和長整數更具互換性的工作。在 1.5.2 中,為 Solaris 新增了大型檔案支援,允許讀取大於 2 GiB 的檔案;這使得檔案物件的 tell() 方法改為回傳長整數而不是一般整數。某些程式碼會將兩個檔案偏移量相減並嘗試使用結果來乘以序列或切片字串,但這會引發 TypeError。在 2.0 中,長整數可以用於乘以或切片序列,它會按你直覺預期的方式運作;3L * 'abc' 會產生 'abcabcabc'、(0,1,2,3)[2L:4L] 會產生 (2,3)。長整數也可以用於以前只接受整數的各種情況,例如檔案物件的 seek() 方法,以及 % 運算子支援的格式(%d%i%x 等)。例如 "%d" % 2L**64 會產生字串 18446744073709551616

所有長整數變更中最微妙的是長整數的 str() 不再有尾隨的 'L' 字元,儘管 repr() 仍然包含它。'L' 讓許多想要印出看起來像普通整數的長整數的人感到惱火,因為他們必須特意去掉這個字元。在 2.0 中這不再是問題,但執行 str(longval)[:-1] 並假設 'L' 存在的程式碼現在會丟失最後一個數字。

取浮點數的 repr() 現在使用與 str() 不同的格式化精度。repr() 對 C 的 sprintf() 使用 %.17g 格式字串,而 str() 像以前一樣使用 %.12g。效果是對於某些數字,repr() 可能偶爾會顯示比 str() 更多的小數位。例如數字 8.1 無法用二進位精確表示,所以 repr(8.1)'8.0999999999999996',而 str(8.1) 是 '8.1'

-X 命令列選項已被移除,該選項會將所有標準例外轉換為字串而不是類別;現在標準例外將始終是類別。包含標準例外的 exceptions 模組已從 Python 翻譯為內建的 C 模組,這由 Barry Warsaw 和 Fredrik Lundh 編寫。

擴充/嵌入變更

有些變更是在底層,只有編寫 C 擴充模組或將 Python 直譯器嵌入到較大應用程式中的人才會注意到。如果你不處理 Python 的 C API,可以安全地跳過本節。

Python C API 的版本號已增加,因此為 1.5.2 編譯的 C 擴充必須重新編譯才能與 2.0 一起使用。在 Windows 上由於 Windows DLL 的運作方式,Python 2.0 無法引入為 Python 1.5.x 建立的第三方擴充,因此 Python 會引發例外且引入會失敗。

Jim Fulton 的 ExtensionClass 模組使用者會高興地發現掛鉤已被添加,使 ExtensionClass 現在有被 isinstance()issubclass() 支援。這意味著你不再需要記得編寫像 if type(obj) == myExtensionClass 這樣的程式碼,而是可以使用更自然的 if isinstance(obj, myExtensionClass)

Python/importdl.c 檔案以前是一堆 #ifdef 來支援許多不同平台上的動態載入,這已被 Greg Stein 清理和重組。importdl.c 現在相當小,平台特定的程式碼已移至一系列 Python/dynload_*.c 檔案中。有另一個清理工作:Include/ 目錄中的許多 my*.h 檔案包含各種為改善可攜性的 hack;它們已被合併到單一檔案 Include/pyport.h

Vladimir Marangozov 完成了備受期待的 malloc 重構,使 Python 直譯器可以輕鬆使用自訂記憶體分配器而不是 C 的標準 malloc()。相關文件請閱讀 Include/pymem.hInclude/objimpl.h 中的註解。敲定介面的冗長討論,請參閱 python.org 上 'patches' 和 'python-dev' 列表的網路存檔。

MacOS 的 GUSI 開發環境的近期版本支援了 POSIX 執行緒。因此,Python 的 POSIX 執行緒支援現在可以在 Macintosh 上運作。也貢獻了有用到使用者空間 GNU pth 函式庫的執行緒支援。

Windows 上的執行緒支援也得到了改進。Windows 支援僅在爭用情況 (contention) 下使用核心 (kernel) 物件的執行緒鎖定;在沒有爭用的常見情況下,它們使用更簡單的函式,速度快一個數量級。NT 上 Python 1.5.2 的執行緒版本比非執行緒版本慢兩倍;經過 2.0 的變更,差異只有 10%。這些改進由 Yakov Markovitch 貢獻。

Python 2.0 的原始碼現在只使用 ANSI C 原型,因此編譯 Python 現在需要 ANSI C 編譯器,不能再使用只支援 K&R C 的編譯器。

以前 Python 虛擬機在其位元組碼中使用 16 位元數字,限制了原始檔的大小。特別是這影響了 Python 原始碼中字面串列和字典的最大大小;偶爾生成 Python 程式碼的人會遇到這個限制。Charles G. Waldman 的補丁將限制從 2**16 提高到 2**32

新增了三個便利函式,以用於在模組初始化時將常數加入模組的字典:PyModule_AddObject()PyModule_AddIntConstant()PyModule_AddStringConstant()。這些函式分別接受一個模組物件、一個包含要新增名稱的以 null 結尾的 C 字串,以及一個用於指定給該名稱的值的第三個引數。這第三個引數分別是 Python 物件、C long 或 C 字串。

為 Unix 風格的訊號處理程式新增了一個包裝器 API。PyOS_getsig() 會取得訊號處理程式,PyOS_setsig() 會設定新的處理程式。

Distutils:讓模組安裝變簡單

在 Python 2.0 之前,安裝模組是一件繁瑣的事情 —— 沒有辦法自動找出 Python 安裝在哪裡,或者擴充模組應該使用什麼編譯器選項。軟體作者必須經歷編輯 Makefile 和設定檔的艱苦過程,而這些只真正適用於 Unix,使得 Windows 和 MacOS 不受支援。不同擴充套件會有迥異的安裝方式,使得 Python 使用者們需要面對歧異度過高的各種安裝指南,使得管理 Python 安裝成為一件苦差事。

由 Greg Ward 領導的發布工具 SIG 建立了 Distutils,一個讓套件安裝更容易的系統。它們構成了 distutils 套件,是 Python 標準函式庫新的部分。在最佳情況下,從原始碼安裝 Python 模組只需要相同的步驟:首先你只需解壓縮 tarball 或 zip 檔案,然後執行 "python setup.py install",這就會自動偵測平台、識別編譯器、編譯 C 擴充模組,並將發布板安裝到適當的目錄。可選的命令列引數提供對安裝過程的更多控制,distutils 套件提供了許多能夠覆寫預設值的地方 —— 將建置與安裝分開、在非預設目錄中建置或安裝等等。

為了使用 Distutils,你需要編寫一個 setup.py 腳本。對於簡單的情況,當軟體只包含 .py 檔案時,一個最小的 setup.py 可能只有幾行:

from distutils.core import setup
setup (name = "foo", version = "1.0",
       py_modules = ["module1", "module2"])

如果軟體由數個套件組成的話,setup.py 檔案也不會複雜太多:

from distutils.core import setup
setup (name = "foo", version = "1.0",
       packages = ["package", "package.subpackage"])

C 擴充可能是最複雜的情況;這是一個取自 PyXML 套件的範例:

from distutils.core import setup, Extension

expat_extension = Extension('xml.parsers.pyexpat',
     define_macros = [('XML_NS', None)],
     include_dirs = [ 'extensions/expat/xmltok',
                      'extensions/expat/xmlparse' ],
     sources = [ 'extensions/pyexpat.c',
                 'extensions/expat/xmltok/xmltok.c',
                 'extensions/expat/xmltok/xmlrole.c', ]
       )
setup (name = "PyXML", version = "0.5.4",
       ext_modules =[ expat_extension ] )

Distutils 也可以建立原始碼和二進位發布板。透過 "python setup.py sdist" 執行的 "sdist" 命令會建立原始碼發布板,如 foo-1.0.tar.gz。新增命令並不困難,"bdist_rpm" 和 "bdist_wininst" 命令已被貢獻,分別用於建立軟體的 RPM 發布板和 Windows 安裝程式。建立其他發行格式(如 Debian 套件和 Solaris .pkg 檔案)的命令正處於不同的開發階段。

所有這些都記錄在新的手冊 Distributing Python Modules 中,該手冊加入了 Python 的基本文件集。

XML 模組

Python 1.5.2 包含了一個以 xmllib 模組形式提供的簡單 XML 剖析器,其由 Sjoerd Mullender 所貢獻。自 1.5.2 發布以來,兩種不同的 XML 處理介面變得常見:SAX2(Simple API for XML 的第二版)提供了一個事件驅動的介面,它與 xmllib 有些相似,而 DOM(Document Object Model,文件物件模型)提供了一個基於樹狀結構的介面,將 XML 文件轉換為可以遍歷和修改的節點樹。Python 2.0 包含了 SAX2 介面和精簡的 DOM 介面,作為 xml 套件的一部分。這裡我們將簡要概述這些新介面;請查閱 Python 文件或原始碼以取得完整詳情。Python XML SIG 也正在改進文件。

SAX2 支援

SAX 定義了一個用於剖析 XML 的事件驅動介面。要使用 SAX,你必須編寫一個 SAX 處理程式類別。處理程式類別繼承自 SAX 提供的各種類別,並覆寫各種方法,然後這些方法會被 XML 剖析器呼叫。例如,startElement()endElement() 方法會在剖析器遇到每個開始標籤和結束標籤時被呼叫,characters() 方法會在每個字元資料區塊時被呼叫,依此類推。

事件驅動方法的優點是整個文件不必在任何時候都駐留在記憶體中,如果你正在處理非常龐大的文件,這一點會很重要。然而如果你試圖以某種複雜的方式修改文件結構,編寫 SAX 處理程式類別可能會變得非常複雜。

例如這個定義了一個處理程式的小範例程式,它會為每個開始標籤和結束標籤印出訊息,然後使用它來剖析檔案 hamlet.xml

from xml import sax

class SimpleHandler(sax.ContentHandler):
    def startElement(self, name, attrs):
        print 'Start of element:', name, attrs.keys()

    def endElement(self, name):
        print 'End of element:', name

# 建立剖析器物件
parser = sax.make_parser()

# 告訴它使用哪個處理程式
handler = SimpleHandler()
parser.setContentHandler( handler )

# 剖析檔案!
parser.parse( 'hamlet.xml' )

如需更多資訊,請查閱 Python 文件,或位於 https://pyxml.sourceforge.net/topics/howto/xml-howto.html 的 XML HOWTO。

DOM 支援

文件物件模型是 XML 文件的樹狀表示法。頂層 Document 實例是樹的根,有一個子節點是頂層 Element 實例。這個 Element 有子節點代表字元資料和任何子元素,這些子元素可能又有自己的子節點,依此類推。使用 DOM 你可以用任何你喜歡的方式遍歷結果樹、存取元素和屬性值、插入和刪除節點,並將樹轉換回 XML。

DOM 對於修改 XML 文件很有用,因為你可以建立 DOM 樹、透過新增節點或重新排列子樹來修改它,然後產生新的 XML 文件作為輸出。你也可以手動建構 DOM 樹並將其轉換為 XML,這可能比簡單地將 <tag1>...</tag1> 寫入檔案更靈活地產生 XML 輸出。

Python 附帶的 DOM 實作位於 xml.dom.minidom 模組中。它是 Level 1 DOM 的輕量級實作,支援 XML 命名空間。它提供了 parse()parseString() 便利函式來產生 DOM 樹:

from xml.dom import minidom
doc = minidom.parse('hamlet.xml')

doc 是一個 Document 實例。Document 與所有其他 DOM 類別(如 ElementText)一樣,是 Node 基礎類別的子類別。因此 DOM 樹中的所有節點都支援某些共同方法,例如 toxml() 會回傳一個包含節點及其子節點的 XML 表示的字串。每個類別也有自己的特殊方法;例如 ElementDocument 實例有一個方法可以找到所有具有給定標籤名稱的子元素。從前面的 2 行範例繼續:

perslist = doc.getElementsByTagName( 'PERSONA' )
print perslist[0].toxml()
print perslist[1].toxml()

對於 Hamlet XML 檔案,以上幾行會輸出:

<PERSONA>CLAUDIUS, king of Denmark. </PERSONA>
<PERSONA>HAMLET, son to the late, and nephew to the present king.</PERSONA>

文件的根元素可透過 doc.documentElement 取得,其子節點可以透過刪除、新增或移除節點輕鬆修改:

root = doc.documentElement

# 移除第一個子節點
root.removeChild( root.childNodes[0] )

# 將新的第一個子節點移到末尾
root.appendChild( root.childNodes[0] )

# 將新的第一個子節點(原本是第三個子節點)
# 插入到第 20 個子節點之前。
root.insertBefore( root.childNodes[0], root.childNodes[20] )

我會再次將你引導至相對應的 Python 文件以取得不同 Node 類別及其各種方法的完整列表。

與 PyXML 的關係

XML 專項興趣小組 (Special Interest Group) 這段時間以來一直在開發與 XML 相關的 Python 程式碼。它的程式碼發布版稱為 PyXML,可從 SIG 的網頁 https://www.python.org/community/sigs/current/xml-sig 取得。PyXML 發布版也使用 xml 作為套件名稱。如果你編寫過使用 PyXML 的程式,你可能想知道它與 2.0 xml 套件的相容性。

答案是 Python 2.0 的 xml 套件與 PyXML 不相容,但可以透過安裝最新版本的 PyXML 來使其相容。許多應用程式可以使用 Python 2.0 附帶的 XML 支援,但更複雜的應用程式需要安裝完整的 PyXML 套件。安裝後,PyXML 0.6.0 或更高版本將取代 Python 附帶的 xml 套件,成為標準套件的嚴格超集,並新增一系列額外功能。PyXML 中的一些額外功能包括:

  • 4DOM,來自 FourThought, Inc. 的完整 DOM 實作。

  • xmlproc 驗證剖析器,由 Lars Marius Garshol 編寫。

  • sgmlop 剖析器加速模組,由 Fredrik Lundh 編寫。

模組變更

對 Python 龐大的標準函式庫進行了大量改進和錯誤修正;一些受影響的模組包括 readlineConfigParsercgicalendarposixreadlinexmllibaifcchunkwaverandomshelvenntplib。請查閱 CVS 日誌以取得補丁的確切詳情。

Brian Gallew 為 socket 模組貢獻了 OpenSSL 支援。OpenSSL 是 Secure Socket Layer 的實作,它會加密透過 socket 發送的資料。編譯 Python 時,你可以編輯 Modules/Setup 來包含 SSL 支援,這會在 socket 模組中新增一個額外的函式:socket.ssl(socket, keyfile, certfile),它接受一個 socket 物件並回傳一個 SSL socket。httpliburllib 模組也被修改以支援 https:// URL,儘管還沒有人實作基於 SSL 的 FTP 或 SMTP。

httplib 模組已由 Greg Stein 重寫以支援 HTTP/1.1。

提供了與 1.5 版本 httplib 的向後相容性,但使用 HTTP/1.1 功能(如管線化 (pipelining))將需要重寫程式碼以使用不同的介面集。

Tkinter 模組現在支援 Tcl/Tk 版本 8.1、8.2 或 8.3,並且已放棄對舊版 7.x 的支援。Tkinter 模組現在支援在 Tk widget 中顯示 Unicode 字串。此外,Fredrik Lundh 貢獻了一項最佳化,使像 create_linecreate_polygon 這樣的操作變快許多,特別是在使用大量座標時。

curses 模組已大幅擴展,從 Oliver Andrich 的改進版本開始,提供了許多來自 ncurses 和 SYSV curses 的額外函式,如顏色、替代字元集支援、pad 和滑鼠支援。這意味著該模組不再與只有 BSD curses 的作業系統相容,但似乎沒有任何目前仍在維護的作業系統屬於這個類別。

如同先前關於 2.0 Unicode 支援的討論中提到的,re 模組提供的正規表示式底層實作已經改變。SRE 是一個由 Fredrik Lundh 編寫並由 Hewlett Packard 部分資助的新正規表示式引擎,支援對 8 位元字串和 Unicode 字串進行匹配。

新增模組

新增了一些模組。我們僅在此列出簡要描述;請參閱 2.0 說明文件以了解特定模組的詳細資訊。

  • atexit:用於註冊在 Python 直譯器退出前要呼叫的函式。目前直接設定 sys.exitfunc 的程式碼應改為使用 atexit 模組,引入 atexit 並使用要在退出時呼叫的函式呼叫 atexit.register()。(由 Skip Montanaro 貢獻。)

  • codecsencodingsunicodedata:作為新 Unicode 支援的一部分而新增。

  • filecmp:取代舊的 cmpcmpcachedircmp 模組,這些模組現已被棄用。(由 Gordon MacMillan 和 Moshe Zadka 貢獻。)

  • gettext:此模組透過提供 GNU gettext 訊息目錄函式庫的介面,為 Python 程式提供國際化 (I18N) 和本地化 (L10N) 支援。(由 Barry Warsaw 整合,來自 Martin von Löwis、Peter Funk 和 James Henstridge 的個別貢獻。)

  • linuxaudiodev:支援 Linux 上的 /dev/audio 裝置,是現有 sunaudiodev 模組的孿生模組。(由 Peter Bosch 貢獻、Jeremy Hylton 修正。)

  • mmap:Windows 和 Unix 上記憶體對映檔案的介面。檔案的內容可以直接對映到記憶體中,此時它的行為就像一個可變字串,因此可以讀取和修改其內容。它們甚至可以傳遞給預期要接受普通字串的函式,例如 re 模組。(由 Sam Rushing 貢獻,A.M. Kuchling 進行了一些擴展。)

  • pyexpat:Expat XML 剖析器的介面。(由 Paul Prescod 貢獻。)

  • robotparser:剖析 robots.txt 檔案,用於編寫能禮貌地避開網站某些區域的網路爬蟲。剖析器接受 robots.txt 檔案的內容,並從中建立一組規則,然後可以回答關於給定 URL 是否可抓取的問題。(由 Skip Montanaro 貢獻。)

  • tabnanny:一個用於檢查 Python 原始碼中不明確縮排的模組/腳本。(由 Tim Peters 貢獻。)

  • UserString:一個用於衍生行為類似字串的物件的基礎類別。

  • webbrowser:一個不相依於平台、能以特定 URL 啟動網頁瀏覽器的模組。對於各個平台,會依特定順序嘗試各種瀏覽器。使用者可以透過設定 BROWSER 環境變數來更改啟動哪個瀏覽器。(最初受到 Eric S. Raymond 對 urllib 的補丁啟發,該補丁新增了類似功能,但最終模組來自 Fred Drake 最初實作的 Tools/idle/BrowserControl.py 程式碼,並由 Fred 改編為標準函式庫。)

  • _winreg:Windows 登錄檔的介面。_winreg 是自 1995 年以來一直是 PythonWin 一部分函式的改編版本,但現在已被加入核心發布板,並被改進以支援 Unicode。_winreg 由 Bill Tutt 和 Mark Hammond 編寫。

  • zipfile:一個用於讀取和寫入 ZIP 格式封存檔案的模組。這些是由 DOS/Windows 上的 PKZIP 或 Unix 上的 zip 產生的封存檔案,不要與 gzip格式的檔案(由 gzip 模組支援)混淆。(由 James C. Ahlstrom 貢獻。)

  • imputil:一個與現有的 ihooks 模組相比,提供更簡單方式來編寫自訂引入掛鉤的模組。(由 Greg Stein 實作,過程中在 python-dev 上有許多討論。)

IDLE 改進

IDLE 是官方的 Python 跨平台 IDE,使用 Tkinter 編寫。Python 2.0 包含 IDLE 0.6,新增了許多新功能和改進。部分列表如下:

  • UI 改進和最佳化,特別是在語法突顯 (syntax highlighting) 和自動縮排 (auto-indentation) 方面。

  • 類別瀏覽器現在顯示更多資訊,例如模組中的頂層函式。

  • Tab 寬度現在是使用者可設定的選項。開啟現有的 Python 檔案時,IDLE 會自動偵測縮排慣例並調整。

  • 現在支援在各種平台上呼叫瀏覽器,用於在瀏覽器中開啟 Python 說明文件。

  • IDLE 現在有一個命令列,與原版 Python 直譯器大致相似。

  • 在許多地方新增了呼叫提示。

  • IDLE 現在可以作為套件安裝。

  • 在編輯器視窗中,底部現在有一個行/列狀態列。

  • 三個新的按鍵命令:檢查模組(Alt-F5)、引入模組(F5)和執行腳本(Ctrl-F5)。

已刪除和已棄用的模組

一些模組因為已過時或因為現在有更好的方法來完成相同的事情而被移除。stdwin 模組已被移除;它是用於一個不再開發的與平台無關的視窗工具組。

許多模組已被移至 lib-old 子目錄:cmpcmpcachedircmpdumpfindgreppackmailpolyutilwhatsoundzmod。如果你的程式碼依賴於已被移至 lib-old 的模組,你可以簡單地將該目錄加入 sys.path 來取回它們,但建議你更新任何使用這些模組的程式碼。

致謝

作者想感謝以下對本文各版本草稿提供建議的協力者:David Bolen、Mark Hammond、Gregg Hauser、Jeremy Hylton、Fredrik Lundh、Detlef Lannert、Aahz Maruch、Skip Montanaro、Vladimir Marangozov、Tobias Polzin、Guido van Rossum、Neil Schemenauer 和 Russ Schmidt。