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 支援。當然,在 5 月之後開發仍在繼續,因此 1.6 樹收到了一些修復,以確保它與 Python 2.0 向前相容。因此,1.6 是 Python 演變的一部分,而不是一個分支。
那麼,您應該對 Python 1.6 感興趣嗎?可能不會。1.6final 和 2.0beta1 版本在同一天(2000 年 9 月 5 日)釋出,計劃在一個月左右完成 Python 2.0。如果您有應用程式需要維護,那麼透過遷移到 1.6、修復它們,然後在 2.0 遷移後一個月內再次出現中斷,似乎沒有什麼意義;您最好直接轉到 2.0。本文件中描述的大多數真正有趣的功能僅在 2.0 中,因為在 5 月到 9 月之間完成了大量工作。
新的開發流程¶
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 的服務導致開發速度顯著提高。現在,提交補丁、對其進行評論、由提交者以外的人員修改,並在人員之間來回傳遞,直到認為該補丁值得簽入為止。在一箇中心位置跟蹤錯誤,可以將其分配給特定的人員進行修復,並且我們可以計算未解決的錯誤數量來衡量進度。這並非沒有代價:開發人員現在需要處理更多的電子郵件、需要關注更多的郵件列表,並且需要為新環境編寫特殊工具。例如,SourceForge 傳送的預設補丁和錯誤通知電子郵件訊息完全沒有用處,因此 Ka-Ping Yee 編寫了一個 HTML 螢幕抓取器,可以傳送更有用的訊息。
新增程式碼的便利性導致了一些最初的成長的煩惱,例如程式碼在準備就緒之前就被簽入,或者沒有得到開發人員組的明確同意。出現的批准過程與 Apache 組使用的過程有些相似。開發人員可以對補丁投票 +1、+0、-0 或 -1;+1 和 -1 表示接受或拒絕,而 +0 和 -0 表示開發人員對更改大多漠不關心,但略有正面或負面的傾向。與 Apache 模型最顯著的區別在於,投票本質上是諮詢性的,讓擁有終身仁慈獨裁者地位的 Guido van Rossum 知道總體意見是什麼。他仍然可以忽略投票結果,即使社群不同意他,也可以批准或拒絕更改。
生成實際補丁是新增新功能的最後一步,與早期提出良好設計的任務相比,通常很容易。對新功能的討論通常會爆發成冗長的郵件列表執行緒,使得討論難以跟進,而且沒有人可以閱讀 python-dev 的每篇文章。因此,已經建立了一個相對正式的流程來編寫 Python 增強提案 (PEP),該流程以網際網路 RFC 流程為模型。PEP 是描述擬議的新功能的草案檔案,並且會不斷修訂,直到社群達成共識,要麼接受該提案,要麼拒絕該提案。引用 PEP 1 “PEP 目的和指南”的介紹:
PEP 代表 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 整合”。本文將僅介紹有關 Unicode 介面的最重要幾點。
在 Python 原始碼中,Unicode 字串寫為 u"string"
。可以使用新的轉義序列 \uHHHH
寫入任意 Unicode 字元,其中 HHHH 是從 0000 到 FFFF 的 4 位十六進位制數。也可以使用現有的 \xHH
轉義序列,並且可以將八進位制轉義用於 U+01FF 之前的字元,該字元由 \777
表示。
Unicode 字串與常規字串一樣,是一種不可變的序列型別。可以對其進行索引和切片,但不能就地修改。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 是一個包含 Unicode 字串的一部分(可能全部)轉換為給定編碼的 8 位字串,而 length 告訴您有多少 Unicode 字串被轉換。decode_func 與 encode_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()
可透過 re
模組使用 Unicode 感知的正則表示式,該模組有一個由 Secret Labs AB 的 Fredrik Lundh 編寫的名為 SRE 的新底層實現。
添加了一個 -U
命令列選項,該選項會使 Python 編譯器將所有字串文字解釋為 Unicode 字串文字。這旨在用於測試和未來證明您的 Python 程式碼,因為 Python 的未來版本可能會放棄對 8 位字串的支援,而僅提供 Unicode 字串。
列表推導式¶
列表是 Python 中常用的資料型別,許多程式會在某個時刻操作列表。列表上的兩個常見操作是迴圈遍歷它們,並挑選出滿足特定條件的元素,或將某個函式應用於每個元素。例如,給定一個字串列表,您可能想要提取所有包含給定子字串的字串,或者從每行中刪除尾隨的空格。
現有的 map()
和 filter()
函式可以用於此目的,但它們需要一個函式作為引數之一。如果有一個現有的內建函式可以直接傳遞,這很好,但如果沒有,您必須建立一個小函式來完成所需的工作,並且如果這個小函式需要其他資訊,Python 的作用域規則會使結果變得難看。以前面段落中的第一個示例為例,查詢列表中所有包含給定子字串的字串。您可以編寫以下程式碼來完成它
# Given the list L, make a list of all strings
# containing the substring 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):
# Append the value of
# the expression to the
# resulting list.
這意味著當存在多個 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 正在建立元組,則必須用括號將其括起來。下面的第一個列表推導式是語法錯誤,而第二個是正確的
# Syntax error
[ x,y for x in seq1 for y in seq2]
# Correct
[ (x,y) for x in seq1 for y in seq2]
列表推導式的想法最初來自函數語言程式設計語言 Haskell (https://www.haskell.org)。Greg Ewing 最有效地主張將它們新增到 Python 中,並編寫了初始的列表推導式補丁,然後在 python-dev 郵件列表上進行了看似無休止的討論,並由 Skip Montanaro 保持更新。
增強賦值¶
增強賦值運算子,另一個長期請求的功能,已新增到 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 個字串引數的函式,這意味著有 8 種可能的排列組合,以及相應複雜的程式碼。
相反,Python 2.0 將這個問題推給了字串型別,使得字串操作功能可以透過 8 位字串和 Unicode 字串的方法來使用。
>>> 'andrew'.capitalize()
'Andrew'
>>> 'hostname'.replace('os', 'linux')
'hlinuxtname'
>>> 'moshe'.find('sh')
2
有一件事沒有改變,儘管有一個值得注意的愚人節玩笑,那就是 Python 字串是不可變的。因此,字串方法返回新的字串,並且不會修改它們操作的字串。
舊的 string
模組仍然存在以保持向後相容性,但它主要作為新字串方法的前端。
在 2.0 之前的版本中沒有對應物,儘管它們在 JPython 中存在了很長時間,但有兩個方法是 startswith()
和 endswith()
。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 時指定 --without-cycle-gc
開關執行 configure 指令碼,則可以停用迴圈檢測,如果您無法承受哪怕很小的速度損失,或者懷疑迴圈收集存在錯誤。
幾個人解決了這個問題併為解決方案做出了貢獻。Toby Kelsey 編寫了迴圈檢測方法的早期實現。目前的演算法是由 Eric Tiedemann 在訪問 CNRI 期間提出的,Guido van Rossum 和 Neil Schemenauer 編寫了兩個不同的實現,後來由 Neil 整合。在此過程中,許多其他人提出了建議;python-dev 郵件列表的 2000 年 3 月檔案包含了大部分相關討論,尤其是在名為“Python 的引用迴圈收集”和“再次終結”的執行緒中。
其他核心更改¶
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 is a tuple of positional args,
# kw is a dictionary of keyword args
...
現在,print
語句可以透過在 print
後跟 >> file
,將輸出定向到類檔案物件,類似於 Unix shell 中的重定向運算子。以前,您要麼必須使用類檔案物件的 write()
方法,該方法缺乏 print
的便利性和簡單性,要麼您可以為 sys.stdout
分配一個新值,然後恢復舊值。對於將輸出傳送到標準錯誤,這樣寫要容易得多
print >> sys.stderr, "Warning: action field not supplied"
現在可以在匯入模組時重新命名模組,使用語法 import module as name
或 from module import name as othername
。該補丁由 Thomas Wouters 提交。
使用 %
運算子時,可以使用一種新的格式樣式;‘%r’ 將插入其引數的 repr()
。這也是出於對稱性的考慮而新增的,這次是為了與現有的‘%s’格式樣式對稱,該樣式插入其引數的 str()
。例如,'%r %s' % ('abc', 'abc')
返回一個包含 'abc' abc
的字串。
以前,沒有辦法實現一個類來覆蓋 Python 的內建 in
運算子並實現自定義版本。 obj in seq
如果 obj 存在於序列 seq 中則返回 true;Python 透過簡單地嘗試序列的每個索引,直到找到 obj 或遇到 IndexError
為止來計算此值。Moshe Zadka 貢獻了一個補丁,該補丁添加了一個 __contains__()
魔術方法,用於為 in
提供自定義實現。此外,用 C 編寫的新內建物件可以透過序列協議中的新槽來定義 in
對它們意味著什麼。
早期版本的 Python 使用遞迴演算法來刪除物件。深度巢狀的資料結構可能導致直譯器填滿 C 堆疊並崩潰;Christian Tismer 重寫了刪除邏輯以解決此問題。 在一個相關的說明中,比較遞迴物件會無限遞歸併崩潰;Jeremy Hylton 重寫了程式碼以不再崩潰,而是產生有用的結果。 例如,在此程式碼之後
a = []
b = []
a.append(a)
b.append(b)
比較 a==b
返回 true,因為兩個遞迴資料結構是同構的。 請參閱 python-dev 郵件列表 2000 年 4 月存檔中的“trashcan 和 PR#7”執行緒,瞭解導致此實現的討論以及一些有用的相關連結。請注意,比較現在也會引發異常。 在早期版本的 Python 中,即使使用者定義的 __cmp__()
方法遇到錯誤,諸如 cmp(a,b)
之類的比較操作也始終會產生答案,因為產生的異常會被靜默地吞噬。
ActiveState 的 Trent Mick 主要完成了將 Python 移植到 Itanium 處理器上的 64 位 Windows 的工作。(令人困惑的是,Win64 上的 sys.platform
仍然是 '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 以獲取更多說明。
已經嘗試緩解 Python 的一個缺陷,即當代碼在變數被賦值之前引用區域性變數時,經常會引起混淆的 NameError
異常。 例如,以下程式碼在 1.5.2 和 2.0 中的 print
語句上引發異常;在 1.5.2 中,會引發 NameError
異常,而 2.0 會引發新的 UnboundLocalError
異常。 UnboundLocalError
是 NameError
的子類,因此任何期望引發 NameError
的現有程式碼都應該仍然有效。
def f():
print "i=",i
i = i + 1
f()
引入了兩個新的異常,TabError
和 IndentationError
。 它們都是 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 堆疊填滿並導致核心轉儲或 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
並定義預處理器符號 NO_STRICT_LIST_APPEND
以保留舊的行為;不建議這樣做。
socket
模組中的某些函式在這方面仍然很寬容。例如,socket.connect( ('hostname', 25) )
是正確的形式,傳遞一個表示 IP 地址的元組,但是 socket.connect('hostname', 25)
也有效。socket.connect_ex
和 socket.bind
也類似地比較寬鬆。2.0alpha1 收緊了這些函式,但是由於文件實際上使用了錯誤的多個引數形式,許多人編寫的程式碼會在更嚴格的檢查下崩潰。面對公眾的反應,GvR 撤銷了這些更改,因此對於 socket
模組,文件已修復,並且多個引數形式僅被標記為已棄用;在未來的 Python 版本中,它將會再次收緊。
字串字面量中的 \x
轉義現在需要正好 2 個十六進位制數字。以前,它會消耗 ‘x’ 之後的所有十六進位制數字,並取結果的最低 8 位,因此 \x123456
等同於 \x56
。
AttributeError
和 NameError
異常具有更友好的錯誤訊息,其文字類似於 '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 模組的使用者會很高興地發現,已添加了鉤子,以便 isinstance()
和 issubclass()
現在支援 ExtensionClass。這意味著您不再需要記住編寫諸如 if type(obj) == myExtensionClass
之類的程式碼,而是可以使用更自然的 if isinstance(obj, myExtensionClass)
。
Python/importdl.c
檔案(這是一個包含 #ifdef 以支援許多不同平臺上的動態載入的檔案)已由 Greg Stein 清理和重組。importdl.c
現在非常小,並且特定於平臺的程式碼已移至一系列 Python/dynload_*.c
檔案中。另一個清理工作:Include/ 目錄中還有一些 my*.h
檔案,其中包含各種可移植性駭客;它們已合併到一個檔案中,Include/pyport.h
。
Vladimir Marangozov 期待已久的 malloc 重組已完成,以便使 Python 直譯器易於使用自定義分配器而不是 C 的標準 malloc()
。有關文件,請閱讀 Include/pymem.h
和 Include/objimpl.h
中的註釋。對於在其中確定介面的漫長討論,請參閱 python.org 上 ‘patches’ 和 ‘python-dev’ 列表的網路存檔。
最新版本的 MacOS GUSI 開發環境支援 POSIX 執行緒。因此,Python 的 POSIX 執行緒支援現在在 Macintosh 上可用。還提供了使用使用者空間 GNU pth
庫的執行緒支援。
Windows 上的執行緒支援也得到了增強。Windows 僅在發生爭用時才支援使用核心物件的執行緒鎖;在沒有爭用的常見情況下,它們使用更簡單的函式,這些函式的速度要快一個數量級。在 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 還可以負責建立原始碼和二進位制發行版。“sdist”命令由“python setup.py sdist
”執行,構建一個原始碼發行版,例如 foo-1.0.tar.gz
。新增新命令並不困難,“bdist_rpm”和“bdist_wininst”命令已經貢獻出來,分別用於為軟體建立 RPM 發行版和 Windows 安裝程式。建立其他發行版格式(如 Debian 包和 Solaris .pkg
檔案)的命令正處於不同的開發階段。
所有這些都記錄在一本新的手冊《分發 Python 模組》中,該手冊加入了 Python 文件的基本集合。
XML 模組¶
Python 1.5.2 以 xmllib
模組的形式包含了一個簡單的 XML 解析器,該模組由 Sjoerd Mullender 貢獻。自 1.5.2 釋出以來,兩種不同的 XML 處理介面變得很常見:SAX2(簡單 XML API 的第 2 版)提供了一個事件驅動的介面,與 xmllib
有些相似之處,而 DOM(文件物件模型)提供了一個基於樹的介面,將 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
# Create a parser object
parser = sax.make_parser()
# Tell it what handler to use
handler = SimpleHandler()
parser.setContentHandler( handler )
# Parse a file!
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 類,例如 Element
和 Text
,是 Node
基類的子類。因此,DOM 樹中的所有節點都支援某些通用方法,例如 toxml()
,它返回一個包含節點及其子節點的 XML 表示形式的字串。每個類也有自己的特殊方法;例如,Element
和 Document
例項有一個方法來查詢所有具有給定標記名稱的子元素。繼續前面的兩行示例
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
# Remove the first child
root.removeChild( root.childNodes[0] )
# Move the new first child to the end
root.appendChild( root.childNodes[0] )
# Insert the new first child (originally,
# the third child) before the 20th child.
root.insertBefore( root.childNodes[0], root.childNodes[20] )
再次,我將您推薦到 Python 文件,以獲得不同的 Node
類及其各種方法的完整列表。
與 PyXML 的關係¶
XML 特殊興趣小組已經從事 XML 相關的 Python 程式碼一段時間了。它的程式碼發行版名為 PyXML,可從 SIG 的網頁 https://python.club.tw/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 的擴充套件標準庫進行了許多改進和錯誤修復;一些受影響的模組包括 readline
、 ConfigParser
、 cgi
、 calendar
、 posix
、 readline
、 xmllib
、 aifc
、 chunk
、 wave
、 random
、 shelve
和 nntplib
。請查閱 CVS 日誌以獲取逐個補丁的詳細資訊。
Brian Gallew 為 socket
模組貢獻了 OpenSSL 支援。OpenSSL 是安全套接字層的實現,它對透過套接字傳送的資料進行加密。在編譯 Python 時,您可以編輯 Modules/Setup
以包含 SSL 支援,這會在 socket
模組中新增一個額外的函式:socket.ssl(socket, keyfile, certfile)
,它接受一個套接字物件並返回一個 SSL 套接字。httplib
和 urllib
模組也進行了更改以支援 https://
URL,儘管沒有人實現基於 SSL 的 FTP 或 SMTP。
httplib
模組已由 Greg Stein 重寫以支援 HTTP/1.1。
提供了與 1.5 版本 httplib
的向後相容性,儘管使用諸如流水線之類的 HTTP/1.1 功能將需要重寫程式碼以使用不同的介面集。
Tkinter
模組現在支援 Tcl/Tk 版本 8.1、8.2 或 8.3,並且已刪除對舊的 7.x 版本的支援。Tkinter 模組現在支援在 Tk 小部件中顯示 Unicode 字串。此外,Fredrik Lundh 貢獻了一個最佳化,使諸如 create_line
和 create_polygon
之類的操作更快,尤其是在使用大量座標時。
curses
模組已大大擴充套件,從 Oliver Andrich 的增強版本開始,提供了來自 ncurses 和 SYSV curses 的許多附加功能,例如顏色、備用字元集支援、填充和滑鼠支援。這意味著該模組不再與僅具有 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 貢獻。)codecs
、encodings
、unicodedata
:作為新的 Unicode 支援的一部分新增。filecmp
:取代了舊的cmp
、cmpcache
和dircmp
模組,這些模組現在已被棄用。(由 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 上啟動 Web 瀏覽器。對於每個平臺,會按照特定的順序嘗試各種瀏覽器。使用者可以透過設定 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 改進和最佳化,尤其是在語法高亮和自動縮排方面。
類瀏覽器現在顯示更多資訊,例如模組中的頂級函式。
製表符寬度現在是一個使用者可設定的選項。當開啟現有的 Python 檔案時,IDLE 會自動檢測縮排約定並進行調整。
現在支援在各種平臺上呼叫瀏覽器,用於在瀏覽器中開啟 Python 文件。
IDLE 現在有一個命令列,它與普通的 Python 直譯器非常相似。
在許多地方添加了呼叫提示。
IDLE 現在可以作為軟體包安裝。
在編輯器視窗中,底部現在有一個行/列欄。
三個新的快捷鍵命令:檢查模組(Alt-F5),匯入模組(F5)和執行指令碼(Ctrl-F5)。
刪除和棄用的模組¶
一些模組已被刪除,因為它們已過時,或者因為現在有更好的方法來做同樣的事情。stdwin
模組已消失;它用於一個不再開發的平臺無關的視窗工具包。
許多模組已移動到 lib-old
子目錄:cmp
, cmpcache
, dircmp
, dump
, find
, grep
, packmail
, poly
, util
, whatsound
, zmod
。如果您的程式碼依賴於已移動到 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。