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 提供的工具來儲存原始碼、跟蹤 bug 報告以及管理補丁提交佇列。要報告 Python 2.0 的 bug 或提交補丁,請使用 Python 專案頁面(位於 https://sourceforge.net/projects/python/)提供的 bug 跟蹤和補丁管理器工具。
目前託管在 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 服務顯著提高了開發速度。補丁現在被提交、評論、由原始提交者以外的人修改,並在人們之間來回傳遞,直到補丁被認為值得簽入。Bug 在一箇中心位置進行跟蹤,可以分配給特定的人進行修復,我們可以統計未解決的 bug 數量來衡量進度。這並非沒有代價:開發人員現在需要處理更多的電子郵件,關注更多的郵件列表,並且必須為新環境編寫專用工具。例如,SourceForge 傳送的預設補丁和 bug 通知電子郵件完全沒有幫助,因此 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"
。任意 Unicode 字元可以使用新的轉義序列 \uHHHH
來書寫,其中 HHHH 是一個 4 位十六進位制數字,範圍從 0000 到 FFFF。現有的 \xHH
轉義序列也可以使用,八進位制轉義序列可以用於 U+01FF 及以下的字元,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 字串總是強制轉換為 Unicode,使用預設的 ASCII 編碼;'a' + u'bc'
的結果是 u'abc'
。
已新增新的內建函式,並修改了現有內建函式以支援 Unicode
unichr(ch)
返回一個長度為 1 的 Unicode 字串,包含字元 ch。ord(u)
,其中 u 是一個單字元的普通字串或 Unicode 字串,返回該字元的數值(整數)。unicode(string [, encoding] [, errors] )
從一個 8 位字串建立一個 Unicode 字串。encoding
是一個字串,指定要使用的編碼。errors
引數指定對當前編碼無效的字元的處理方式;將'strict'
作為值傳遞會在任何編碼錯誤時引發異常,而'ignore'
會靜默忽略錯誤,'replace'
則在出現任何問題時使用官方替換字元 U+FFFD。exec
語句,以及各種內建函式如eval()
、getattr()
和setattr()
也將接受 Unicode 字串以及普通字串。(很可能修復此問題時遺漏了一些內建函式;如果您發現接受字串但不接受 Unicode 字串的內建函式,請將其報告為 bug。)
新模組 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_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 個字串引數,這意味著有八種可能的排列,以及相應複雜的程式碼。
相反,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 能夠透過仔細調整將開銷最小化。目前尚不清楚會損失多少效能,因為對此進行基準測試很棘手,並且關鍵取決於程式建立和銷燬物件的頻率。如果無法承受哪怕一點點速度損失,或者懷疑迴圈收集存在 bug,則可以在編譯 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)
呼叫函式 f()
,其中引數元組為 args,關鍵字引數位於字典 kw 中。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 月存檔中標題為“垃圾箱和 PR#7”的執行緒。請注意,現在比較也可以引發異常。在早期版本的 Python 中,比較操作(例如 cmp(a,b)
)總是會產生一個答案,即使使用者定義的 __cmp__()
方法遇到錯誤,因為由此產生的異常會被悄悄地吞噬。
已經開展了將 Python 移植到 Itanium 處理器上的 64 位 Windows 的工作,主要由 ActiveState 的 Trent Mick 完成。(令人困惑的是,在 Win64 上 sys.platform
仍然是 'win32'
,因為為了便於移植,MS Visual C++ 似乎在 Itanium 上將程式碼視為 32 位。)PythonWin 也支援 Windows CE;有關更多資訊,請參閱 Python CE 頁面 https://pythonce.sourceforge.net/。
另一個新平臺是 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()
函式現在接受一個可選的“基數”引數。int('123', 10)
返回 123,而 int('123', 16)
返回 291。int(123, 16)
會引發 TypeError
異常,並顯示訊息:“無法將非字串轉換為帶有顯式基數的型別”。
一個新的變數已新增到 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 需要正好 1 個引數;給出了 2 個”。修復方法是簡單地新增一對額外的括號,將兩個值作為元組傳遞: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 轉換為由 Barry Warsaw 和 Fredrik Lundh 編寫的內建 C 模組。
擴充套件/嵌入變更¶
某些更改是內部的,只有編寫 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
,它曾是大量 #ifdefs 用於支援在許多不同平臺上進行動態載入,由 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()
。這些函式中的每一個都接受一個模組物件、一個包含要新增的名稱的以空字元結尾的 C 字串,以及一個用於分配給該名稱的值的第三個引數。第三個引數分別是一個 Python 物件、一個 C long 或一個 C 字串。
添加了一個用於 Unix 風格訊號處理程式的包裝 API。PyOS_getsig()
獲取一個訊號處理程式,PyOS_setsig()
設定一個新的處理程式。
Distutils:讓模組易於安裝¶
在 Python 2.0 之前,安裝模組是一件繁瑣的事情——無法自動確定 Python 安裝在哪裡,或者擴充套件模組使用什麼編譯器選項。軟體作者必須經歷編輯 Makefiles 和配置檔案等艱苦的儀式,這些只在 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 包含一個簡單的 XML 解析器,以 Sjoerd Mullender 貢獻的 xmllib
模組的形式出現。自 1.5.2 釋出以來,兩種不同的 XML 處理介面變得普遍:SAX2(XML 簡單 API 的第 2 版)提供了一個事件驅動介面,與 xmllib
有些相似;DOM(文件物件模型)提供了一個基於樹的介面,將 XML 文件轉換為可以遍歷和修改的節點樹。Python 2.0 在 xml
包中包含了一個 SAX2 介面和一個簡化版 DOM 介面。這裡我們將簡要概述這些新介面;有關完整的詳細資訊,請查閱 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 文件,或訪問 XML HOWTO (https://pyxml.sourceforge.net/topics/howto/xml-howto.html)。
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()
對於《哈姆雷特》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 編寫。
由 Fredrik Lundh 編寫的
sgmlop
解析器加速模組。
模組變更¶
Python 龐大的標準庫進行了大量改進和錯誤修復;一些受影響的模組包括 readline
、ConfigParser
、cgi
、calendar
、posix
、readline
、xmllib
、aifc
、chunk
、wave
、random
、shelve
和 nntplib
。有關準確的逐個補丁的詳細資訊,請查閱 CVS 日誌。
Brian Gallew 為 socket
模組貢獻了 OpenSSL 支援。OpenSSL 是安全套接字層 (Secure Socket Layer) 的一種實現,它對透過套接字傳送的資料進行加密。編譯 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 編寫並由惠普部分資助的新正則表示式引擎,支援對 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 改進和最佳化,尤其是在語法高亮和自動縮排方面。
類瀏覽器現在顯示更多資訊,例如模組中的頂級函式。
Tab 寬度現在是一個使用者可設定的選項。開啟現有 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。