Python 2.2 的新特性¶
- 作者:
A.M. Kuchling
引言¶
本文解釋了 Python 2.2.2(2002 年 10 月 14 日釋出)中的新特性。Python 2.2.2 是 Python 2.2(最初於 2001 年 12 月 21 日釋出)的一個錯誤修復版本。
Python 2.2 可以被認為是“清理版本”。它有一些完全新穎的特性,例如生成器和迭代器,但大多數的更改,儘管意義重大且影響深遠,都旨在清理語言設計中不規範和晦澀的角落。
本文不打算提供新特性的完整規範,而是提供一個便捷的概述。有關完整詳細資訊,您應參閱 Python 2.2 的文件,例如 Python 庫參考和 Python 參考手冊。如果您想了解某個特定新特性的完整實現和設計原理,請參閱該特性的 PEP。
PEP 252 和 253:型別和類的變更¶
Python 2.2 中最大且影響最深遠的更改是對 Python 物件和類模型的調整。這些更改應向後相容,因此您的程式碼很可能將繼續執行不變,但這些更改提供了驚人的新功能。在開始本文最長、最複雜的部分之前,我將概述這些更改並提供一些評論。
很久以前我寫了一個網頁,列出了 Python 設計中的缺陷。其中一個最顯著的缺陷是無法子類化用 C 實現的 Python 型別。特別是,無法子類化內建型別,因此您無法僅僅透過子類化(例如)列表來新增一個有用的方法。UserList
模組提供了一個支援所有列表方法並且可以進一步子類化的類,但是有很多 C 程式碼期望的是普通的 Python 列表,而不接受 UserList
例項。
Python 2.2 解決了這個問題,並在此過程中增加了一些令人興奮的新功能。簡要總結:
您可以子類化內建型別,例如列表甚至整數,並且您的子類應該在所有需要原始型別的地方正常工作。
現在除了以前版本的 Python 中可用的例項方法外,還可以定義靜態方法和類方法。
還可以透過使用一種稱為屬性(properties)的新機制,在訪問或設定例項屬性時自動呼叫方法。許多
__getattr__()
的用法都可以重寫為使用屬性,從而使生成的程式碼更簡單、更快。作為一個小的好處,屬性現在也可以有文件字串了。例項的合法屬性列表可以使用槽(slots)限制為特定集合,從而可以防止打字錯誤,並可能在未來版本的 Python 中實現更多最佳化。
一些使用者對所有這些變化表示擔憂。他們說,當然,新功能很棒,並且可以實現以前版本的 Python 中不可能實現的各種技巧,但它們也使語言變得更加複雜。一些人表示,他們一直推薦 Python 是因為它簡單,並覺得它的簡單性正在喪失。
就我個人而言,我認為無需擔心。許多新功能都相當深奧,您無需瞭解它們即可編寫大量 Python 程式碼。編寫一個簡單的類並不比以前困難,因此除非真正需要,否則您無需學習或教授它們。以前只能透過 C 完成的一些非常複雜的任務,現在可以在純 Python 中完成,在我看來,這都是更好的。
我不會試圖涵蓋為使新功能工作所需的所有特殊情況和細微更改。相反,本節只會勾勒出大體輪廓。有關 Python 2.2 新物件模型的更多資訊,請參閱“相關連結”一節 相關連結。
新舊類¶
首先,您應該知道 Python 2.2 確實有兩種類:經典類或舊式類,以及新式類。舊式類模型與早期 Python 版本中的類模型完全相同。本節描述的所有新特性僅適用於新式類。這種分歧並非旨在永久持續;最終舊式類將被廢棄,可能在 Python 3.0 中。
那麼如何定義一個新式類呢?您可以透過子類化一個現有新式類來做到這一點。現在大多數 Python 的內建型別,如整數、列表、字典,甚至檔案,都是新式類。還增加了一個名為 object
的新式類,它是所有內建型別的基類,因此如果沒有合適的內建型別,您只需子類化 object
即可。
class C(object):
def __init__ (self):
...
...
這意味著在 Python 2.2 中,不帶任何基類的 class
語句始終是經典類。(實際上,您也可以透過設定一個名為 __metaclass__
的模組級變數來改變這一點——詳見 PEP 253 ——但直接子類化 object
更簡單。)
內建型別的型別物件作為內建函式提供,使用巧妙的技巧命名。Python 總是擁有名為 int()
、float()
和 str()
的內建函式。在 2.2 中,它們不再是函式,而是被呼叫時充當工廠的型別物件。
>>> int
<type 'int'>
>>> int('123')
123
為了使型別集完整,添加了新的型別物件,例如 dict()
和 file()
。這是一個更有趣的例子,為檔案物件添加了一個 lock()
方法:
class LockableFile(file):
def lock (self, operation, length=0, start=0, whence=0):
import fcntl
return fcntl.lockf(self.fileno(), operation,
length, start, whence)
現已過時的 posixfile
模組包含一個模擬所有檔案物件方法並添加了一個 lock()
方法的類,但此類的例項無法傳遞給期望內建檔案物件的內部函式,而我們新的 LockableFile
則可以。
描述符¶
在以前的 Python 版本中,沒有一致的方法來發現物件支援哪些屬性和方法。有一些非正式的約定,例如定義 __members__
和 __methods__
屬性作為名稱列表,但擴充套件型別或類的作者通常懶得定義它們。您可以回溯到檢查物件的 __dict__
,但當類繼承或任意 __getattr__()
鉤子被使用時,這仍然可能不準確。
新類模型背後的一個重要思想是,透過描述符描述物件屬性的 API 已經形式化。描述符指定屬性的值,說明它是方法還是欄位。有了描述符 API,靜態方法和類方法成為可能,以及更奇特的構造。
屬性描述符是存在於類物件內部的物件,並擁有一些自己的屬性:
__name__
是屬性的名稱。__doc__
是屬性的文件字串。__get__(object)
是從 object 中檢索屬性值的方法。__set__(object, value)
將 object 上的屬性設定為 value。__delete__(object, value)
刪除 object 的 value 屬性。
例如,當您編寫 obj.x
時,Python 實際執行的步驟是:
descriptor = obj.__class__.x
descriptor.__get__(obj)
對於方法,descriptor.__get__
返回一個可呼叫的臨時物件,它封裝了例項和要呼叫的方法。這也是靜態方法和類方法現在成為可能的原因;它們有描述符,僅封裝方法,或封裝方法和類。簡單解釋這些新型別的方法,靜態方法不傳遞例項,因此類似於常規函式。類方法傳遞物件的類,但不傳遞物件本身。靜態方法和類方法定義如下:
class C(object):
def f(arg1, arg2):
...
f = staticmethod(f)
def g(cls, arg1, arg2):
...
g = classmethod(g)
staticmethod()
函式接受函式 f()
,並將其包裝在描述符中返回,以便可以將其儲存在類物件中。您可能期望有特殊的語法來建立此類方法(def static f
、defstatic f()
或類似的東西),但尚未定義此類語法;這已留待未來的 Python 版本。
更多新特性,例如槽(slots)和屬性(properties),也作為新型描述符實現,編寫一個具有新穎功能的描述符類並不困難。例如,可以編寫一個描述符類,使得可以為方法編寫 Eiffel 風格的前置條件和後置條件。使用此特性的類可能定義如下:
from eiffel import eiffelmethod
class C(object):
def f(self, arg1, arg2):
# The actual function
...
def pre_f(self):
# Check preconditions
...
def post_f(self):
# Check postconditions
...
f = eiffelmethod(f, pre_f, post_f)
請注意,使用新的 eiffelmethod()
的人無需瞭解描述符的任何資訊。這就是為什麼我認為新功能不會增加語言的基本複雜性。會有少數嚮導需要了解它才能編寫 eiffelmethod()
或 ZODB 或其他東西,但大多數使用者只需在生成的庫之上編寫程式碼,而忽略實現細節。
多重繼承:菱形法則¶
透過改變名稱解析規則,多重繼承也變得更加有用。考慮這組類(圖表取自 Guido van Rossum 的 PEP 253):
class A:
^ ^ def save(self): ...
/ \
/ \
/ \
/ \
class B class C:
^ ^ def save(self): ...
\ /
\ /
\ /
\ /
class D
經典類的查詢規則很簡單,但不夠智慧;基類是深度優先、從左到右搜尋的。對 D.save()
的引用將搜尋類 D
、B
,然後是 A
,在那裡會找到並返回 save()
。C.save()
根本不會被找到。這很糟糕,因為如果 C
的 save()
方法正在儲存一些特定於 C
的內部狀態,不呼叫它將導致該狀態永遠不會被儲存。
新式類遵循一種不同的演算法,解釋起來有點複雜,但在這種情況下能正確處理。(請注意,Python 2.3 將此演算法更改為在大多數情況下產生相同結果,但對於真正複雜的繼承圖產生更有用結果的演算法。)
列出所有基類,遵循經典查詢規則,如果一個類被重複訪問,則將其多次包含在內。在上面的例子中,被訪問類的列表是 [
D
,B
,A
,C
,A
]。掃描列表以查詢重複的類。如果找到任何重複的類,則刪除除一個以外的所有出現,只保留列表中最後一個。在上面的例子中,在刪除重複項後,列表變為 [
D
,B
,C
,A
]。
遵循此規則,引用 D.save()
將返回 C.save()
,這正是我們想要的行為。此查詢規則與 Common Lisp 遵循的規則相同。一個新的內建函式 super()
提供了一種訪問類的超類而無需重新實現 Python 演算法的方法。最常用的形式是 super(class, obj)
,它返回一個繫結超類物件(而不是實際的類物件)。此形式將在方法中用於呼叫超類中的方法;例如,D
的 save()
方法將如下所示:
class D (B,C):
def save (self):
# Call superclass .save()
super(D, self).save()
# Save D's private information here
...
super()
在呼叫時也可以返回未繫結的超類物件,例如 super(class)
或 super(class1, class2)
,但這可能不常有用。
屬性訪問¶
相當多的複雜 Python 類使用 __getattr__()
定義屬性訪問鉤子;最常見的是為了方便,透過自動將諸如 obj.parent
的屬性訪問對映到諸如 obj.get_parent
的方法呼叫,使程式碼更具可讀性。Python 2.2 增加了一些控制屬性訪問的新方法。
首先,__getattr__(attr_name)
仍然受新式類支援,並且沒有任何改變。與以前一樣,當嘗試訪問 obj.foo
且在例項的字典中未找到名為 foo
的屬性時,它將被呼叫。
新式類也支援一個新方法,__getattribute__(attr_name)
。這兩個方法的區別在於 __getattribute__()
總是在任何屬性被訪問時呼叫,而舊的 __getattr__()
僅在例項的字典中未找到 foo
時呼叫。
然而,Python 2.2 對屬性(properties)的支援通常是捕獲屬性引用的更簡單方法。編寫 __getattr__()
方法很複雜,因為為了避免遞迴,您不能在其中使用常規屬性訪問,而必須修改 __dict__
的內容。__getattr__()
方法最終也會在 Python 檢查其他方法(例如 __repr__()
或 __coerce__()
)時被呼叫,因此必須考慮到這一點來編寫。最後,在每次屬性訪問時呼叫函式會導致顯著的效能損失。
property
是一個新的內建型別,它封裝了三個函式來獲取、設定或刪除屬性,以及一個文件字串。例如,如果您想定義一個可計算但也可設定的 size
屬性,您可以這樣寫:
class C(object):
def get_size (self):
result = ... computation ...
return result
def set_size (self, size):
... compute something based on the size
and set internal state appropriately ...
# Define a property. The 'delete this attribute'
# method is defined as None, so the attribute
# can't be deleted.
size = property(get_size, set_size,
None,
"Storage size of this instance")
這顯然比一對 __getattr__()
/__setattr__()
方法更清晰、更容易編寫,這對方法會檢查 size
屬性並特殊處理,同時從例項的 __dict__
中檢索所有其他屬性。對 size
的訪問也是唯一需要執行函式呼叫工作的,因此對其他屬性的引用以其通常的速度執行。
最後,可以使用新的 __slots__
類屬性來限制物件上可以引用的屬性列表。Python 物件通常非常動態;任何時候都可以透過簡單地執行 obj.new_attr=1
來在例項上定義一個新屬性。新式類可以定義一個名為 __slots__
的類屬性,將合法屬性限制為特定的名稱集合。一個例子會清楚地說明這一點:
>>> class C(object):
... __slots__ = ('template', 'name')
...
>>> obj = C()
>>> print obj.template
None
>>> obj.template = 'Test'
>>> print obj.template
Test
>>> obj.newattr = None
Traceback (most recent call last):
File "<stdin>", line 1, in ?
AttributeError: 'C' object has no attribute 'newattr'
請注意,嘗試賦值給 __slots__
中未列出的屬性時,您會收到 AttributeError
。
PEP 234:迭代器¶
2.2 中另一個重要的補充是在 C 和 Python 級別都引入了迭代介面。物件可以定義呼叫者如何迴圈遍歷它們。
在 Python 2.1 之前的版本中,使 for item in obj
工作通常是透過定義一個 __getitem__()
方法,其形式如下:
def __getitem__(self, index):
return <next item>
__getitem__()
更恰當地用於定義物件的索引操作,以便您可以編寫 obj[5]
來檢索第六個元素。當您僅使用它來支援 for
迴圈時,這有點誤導。考慮一些想要被迴圈的檔案類物件;index 引數基本上是無意義的,因為類可能假設會以 index 每次遞增一的方式進行一系列 __getitem__()
呼叫。換句話說,__getitem__()
方法的存在並不意味著使用 file[5]
隨機訪問第六個元素會起作用,儘管它確實應該如此。
在 Python 2.2 中,迭代可以單獨實現,並且 __getitem__()
方法可以限制為確實支援隨機訪問的類。迭代器的基本思想很簡單。一個新的內建函式 iter(obj)
或 iter(C, sentinel)
用於獲取迭代器。iter(obj)
返回物件 obj 的迭代器,而 iter(C, sentinel)
返回一個迭代器,它將呼叫可呼叫物件 C,直到它返回 sentinel 表示迭代器已完成。
Python 類可以定義一個 __iter__()
方法,該方法應該為物件建立並返回一個新的迭代器;如果物件是其自身的迭代器,則此方法可以直接返回 self
。特別是,迭代器通常會是其自身的迭代器。用 C 實現的擴充套件型別可以實現一個 tp_iter
函式來返回一個迭代器,而希望作為迭代器行為的擴充套件型別可以定義一個 tp_iternext
函式。
那麼,經過這一切,迭代器到底做了什麼?它們有一個必需的方法,next()
,它不接受任何引數並返回下一個值。當沒有更多值可返回時,呼叫 next()
應該丟擲 StopIteration
異常。
>>> L = [1,2,3]
>>> i = iter(L)
>>> print i
<iterator object at 0x8116870>
>>> i.next()
1
>>> i.next()
2
>>> i.next()
3
>>> i.next()
Traceback (most recent call last):
File "<stdin>", line 1, in ?
StopIteration
>>>
在 2.2 中,Python 的 for
語句不再期望一個序列;它期望一個 iter()
將返回迭代器的物件。為了向後相容和方便,對於未實現 __iter__()
或 tp_iter
槽的序列,會自動構造一個迭代器,因此 for i in [1,2,3]
仍然會起作用。無論 Python 直譯器何時迴圈遍歷序列,它都已更改為使用迭代器協議。這意味著您可以執行以下操作:
>>> L = [1,2,3]
>>> i = iter(L)
>>> a,b,c = i
>>> a,b,c
(1, 2, 3)
迭代器支援已新增到 Python 的一些基本型別中。在字典上呼叫 iter()
將返回一個迭代器,該迭代器遍歷其鍵:
>>> m = {'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6,
... 'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12}
>>> for key in m: print key, m[key]
...
Mar 3
Feb 2
Aug 8
Sep 9
May 5
Jun 6
Jul 7
Jan 1
Apr 4
Nov 11
Dec 12
Oct 10
這只是預設行為。如果您想迭代鍵、值或鍵/值對,可以顯式呼叫 iterkeys()
、itervalues()
或 iteritems()
方法以獲取適當的迭代器。在一個次要的相關更改中,in
運算子現在適用於字典,因此 key in dict
現在等價於 dict.has_key(key)
。
檔案也提供一個迭代器,它會呼叫 readline()
方法,直到檔案中沒有更多行。這意味著您現在可以使用以下程式碼讀取檔案的每一行:
for line in file:
# do something for each line
...
請注意,您只能在迭代器中向前移動;無法獲取前一個元素、重置迭代器或複製它。迭代器物件可以提供此類附加功能,但迭代器協議僅要求一個 next()
方法。
參見
- PEP 234 - 迭代器
由 Ka-Ping Yee 和 GvR 撰寫;由 Python Labs 團隊實現,主要由 GvR 和 Tim Peters 完成。
PEP 255:簡單生成器¶
生成器是另一個新功能,它與迭代器的引入相互作用。
您無疑熟悉 Python 或 C 中函式呼叫的工作原理。當您呼叫一個函式時,它會獲得一個私有名稱空間,其中建立其區域性變數。當函式到達 return
語句時,區域性變數被銷燬,結果值返回給呼叫者。稍後對同一函式的呼叫將獲得一組全新的區域性變數。但是,如果區域性變數在函式退出時沒有被丟棄呢?如果您可以稍後從函式中斷的地方恢復執行呢?這就是生成器提供的功能;它們可以被視為可恢復的函式。
這是一個生成器函式最簡單的例子:
def generate_ints(N):
for i in range(N):
yield i
為生成器引入了一個新關鍵字 yield
。任何包含 yield
語句的函式都是生成器函式;Python 的位元組碼編譯器會檢測到這一點,並因此特殊編譯該函式。由於引入了一個新關鍵字,生成器必須在模組中顯式啟用,方法是在模組原始碼的頂部附近包含 from __future__ import generators
語句。在 Python 2.3 中,此語句將變得不必要。
當你呼叫一個生成器函式時,它不會返回一個單一的值;相反,它返回一個支援迭代器協議的生成器物件。執行 yield
語句時,生成器輸出 i
的值,類似於 return
語句。yield
和 return
語句之間的最大區別在於,當到達 yield
時,生成器的執行狀態被掛起,區域性變數被保留。在下一次呼叫生成器的 next()
方法時,函式將緊接著 yield
語句之後恢復執行。(由於複雜的原因,yield
語句不允許出現在 try
…finally
語句的 try
塊內;請閱讀 PEP 255 以獲取 yield
和異常之間互動的完整解釋。)
這是一個 generate_ints()
生成器函式的示例用法:
>>> gen = generate_ints(3)
>>> gen
<generator object at 0x8117f90>
>>> gen.next()
0
>>> gen.next()
1
>>> gen.next()
2
>>> gen.next()
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "<stdin>", line 2, in generate_ints
StopIteration
你也可以寫 for i in generate_ints(5)
,或者 a,b,c = generate_ints(3)
。
在生成器函式內部,return
語句只能在不帶值的情況下使用,並表示值序列的結束;此後生成器不能返回任何進一步的值。帶有值的 return
,例如 return 5
,在生成器函式內部是一個語法錯誤。生成器結果的結束也可以透過手動引發 StopIteration
,或者僅僅讓執行流程落到函式底部來表示。
您可以透過編寫自己的類並將生成器的所有區域性變數儲存為例項變數來手動實現生成器的效果。例如,返回一個整數列表可以透過將 self.count
設定為 0,並讓 next()
方法遞增 self.count
並返回它。然而,對於一箇中等複雜的生成器,編寫相應的類會更加混亂。Lib/test/test_generators.py
包含了一些更有趣的例子。最簡單的一個使用遞迴生成器實現樹的按序遍歷。
# A recursive generator that generates Tree leaves in in-order.
def inorder(t):
if t:
for x in inorder(t.left):
yield x
yield t.label
for x in inorder(t.right):
yield x
Lib/test/test_generators.py
中的另外兩個示例分別給出了 N-皇后問題(在 $NxN$ 棋盤上放置 $N$ 個皇后,使它們互不威脅)和騎士遍歷問題(騎士遍歷 $NxN$ 棋盤上的所有格子,且不重複訪問任何格子)的解決方案。
生成器的思想來源於其他程式語言,尤其是 Icon (https://www2.cs.arizona.edu/icon/),其中生成器的概念是核心。在 Icon 中,每個表示式和函式呼叫都表現得像一個生成器。來自“Icon 程式語言概述” (https://www2.cs.arizona.edu/icon/docs/ipd266.htm) 的一個例子說明了這一點:
sentence := "Store it in the neighboring harbor"
if (i := find("or", sentence)) > 5 then write(i)
在 Icon 中,find()
函式返回子字串“or”被找到的索引:3、23、33。在 if
語句中,i
首先被賦值為 3,但 3 小於 5,所以比較失敗,Icon 用第二個值 23 重新嘗試。23 大於 5,所以比較現在成功,程式碼將值 23 列印到螢幕上。
Python 在將生成器作為核心概念方面並沒有像 Icon 那樣深入。生成器被認為是核心 Python 語言的新組成部分,但學習或使用它們並非強制性的;如果它們不能解決您遇到的任何問題,請隨意忽略它們。Python 介面與 Icon 相比的一個新穎特性是,生成器的狀態表示為一個具體的物件(迭代器),可以傳遞給其他函式或儲存在資料結構中。
參見
- PEP 255 - 簡單生成器
由 Neil Schemenauer、Tim Peters、Magnus Lie Hetland 撰寫。主要由 Neil Schemenauer 和 Tim Peters 實現,並由 Python Labs 團隊進行了其他修復。
PEP 237:統一長整數和整數¶
在最近的版本中,常規整數(在大多數機器上是 32 位值)和長整數(可以是任意大小)之間的區別正變得越來越令人惱火。例如,在支援大於 2**32
位元組檔案的平臺上,檔案物件的 tell()
方法必須返回一個長整數。然而,Python 中有各種期望普通整數的程式碼段,如果提供了長整數,則會引發錯誤。例如,在 Python 1.5 中,只有常規整數可以用作切片索引,並且 'abc'[1L:]
會引發一個 TypeError
異常,並顯示訊息“slice index must be int”。
Python 2.2 將根據需要將值從短整數轉換為長整數。“L”字尾不再需要指示長整數文字,因為現在編譯器將選擇適當的型別。(在未來的 Python 2.x 版本中將不鼓勵使用“L”字尾,在 Python 2.4 中會觸發警告,並可能在 Python 3.0 中取消。)許多以前會引發 OverflowError
的操作現在將返回一個長整數作為結果。例如:
>>> 1234567890123
1234567890123L
>>> 2 ** 64
18446744073709551616L
在大多數情況下,整數和長整數現在將被視為相同。您仍然可以使用內建函式 type()
來區分它們,但這很少需要。
參見
- PEP 237 - 統一長整數和整數
由 Moshe Zadka 和 Guido van Rossum 撰寫。主要由 Guido van Rossum 實現。
PEP 238:更改除法運算子¶
Python 2.2 中最具爭議的改變預示著一項努力的開始,旨在修復 Python 從一開始就存在的一箇舊設計缺陷。目前,Python 的除法運算子 /
在處理兩個整數引數時表現得像 C 的除法運算子:它返回一個整數結果,當有小數部分時,該結果會被截斷。例如,3/2
是 1,而不是 1.5;(-1)/2
是 -1,而不是 -0.5。這意味著除法結果可能會根據兩個運算元的型別而意外地變化,而且由於 Python 是動態型別的,因此很難確定運算元的可能型別。
(爭議在於這是否確實是一個設計缺陷,以及是否值得破壞現有程式碼來修復它。這在 python-dev 上引起了無休止的討論,並在 2001 年 7 月在 comp.lang.python 上爆發了一場尖酸刻薄的帖子風暴。我在此不為任何一方辯護,只描述 2.2 中實現的機制。請閱讀 PEP 238 以獲取論點和反論點的總結。)
由於此更改可能會破壞程式碼,因此它正在逐步引入。Python 2.2 開始了這一過渡,但直到 Python 3.0 才會完全完成切換。
首先,我將借用 PEP 238 中的一些術語。“真除法”是大多數非程式設計師所熟悉的除法:3/2 是 1.5,1/4 是 0.25,依此類推。“地板除法”是 Python 的 /
運算子在給定整數運算元時當前執行的操作;結果是真除法返回值的地板。“經典除法”是 /
當前的混合行為;當運算元為整數時,它返回地板除法的結果,當其中一個運算元為浮點數時,它返回真除法的結果。
以下是 2.2 引入的更改:
一個新的運算子
//
是地板除法運算子。(是的,我們知道它看起來像 C++ 的註釋符號。)//
總是執行地板除法,無論其運算元的型別如何,因此1 // 2
為 0,1.0 // 2.0
也為 0.0。//
在 Python 2.2 中始終可用;您無需使用__future__
語句啟用它。透過在模組中包含
from __future__ import division
,/
運算子將被更改為返回真除法的結果,因此1/2
為 0.5。如果沒有__future__
語句,/
仍然表示經典除法。/
的預設含義在 Python 3.0 之前不會改變。類可以定義名為
__truediv__()
和__floordiv__()
的方法來過載這兩個除法運算子。在 C 級別,PyNumberMethods
結構中也有槽,以便擴充套件型別可以定義這兩個運算子。Python 2.2 支援一些命令列引數,用於測試程式碼是否與更改後的除法語義相容。使用
-Q warn
執行 python 將在對兩個整數執行除法時發出警告。您可以使用此功能查詢受更改影響的程式碼並進行修復。預設情況下,Python 2.2 將直接執行經典除法而不會發出警告;在 Python 2.3 中,警告將預設開啟。
參見
- PEP 238 - 更改除法運算子
由 Moshe Zadka 和 Guido van Rossum 撰寫。由 Guido van Rossum 實現。
Unicode 變更¶
Python 的 Unicode 支援在 2.2 中有所增強。Unicode 字串通常儲存為 UCS-2,即 16 位無符號整數。Python 2.2 也可以編譯為使用 UCS-4,即 32 位無符號整數作為其內部編碼,方法是向 configure 指令碼提供 --enable-unicode=ucs4
。(也可以指定 --disable-unicode
以完全停用 Unicode 支援。)
當構建為使用 UCS-4(“寬 Python”)時,直譯器可以原生處理從 U+000000 到 U+110000 的 Unicode 字元,因此 unichr()
函式的合法值範圍也相應擴大。使用編譯為使用 UCS-2(“窄 Python”)的直譯器時,大於 65535 的值仍將導致 unichr()
引發 ValueError
異常。這一切都在 PEP 261,“支援‘寬’Unicode 字元”中進行了描述;請查閱它以獲取更多詳細資訊。
另一個改變更容易解釋。自引入以來,Unicode 字串一直支援 encode()
方法,用於將字串轉換為所選編碼,如 UTF-8 或 Latin-1。在 2.2 中,8 位字串(但非 Unicode 字串)添加了一個對稱的 decode([*encoding*])
方法。decode()
假定字串採用指定編碼並對其進行解碼,返回編解碼器返回的任何內容。
利用這一新功能,添加了用於與 Unicode 不直接相關的任務的編解碼器。例如,已經為 uu 編碼、MIME 的 base64 編碼以及使用 zlib
模組進行壓縮添加了編解碼器:
>>> s = """Here is a lengthy piece of redundant, overly verbose,
... and repetitive text.
... """
>>> data = s.encode('zlib')
>>> data
'x\x9c\r\xc9\xc1\r\x80 \x10\x04\xc0?Ul...'
>>> data.decode('zlib')
'Here is a lengthy piece of redundant, overly verbose,\nand repetitive text.\n'
>>> print s.encode('uu')
begin 666 <data>
M2&5R92!I<R!A(&QE;F=T:'D@<&EE8V4@;V8@<F5D=6YD86YT+"!O=F5R;'D@
>=F5R8F]S92P*86YD(')E<&5T:71I=F4@=&5X="X*
end
>>> "sheesh".encode('rot-13')
'furrfu'
要將類例項轉換為 Unicode,類可以定義一個 __unicode__()
方法,類似於 __str__()
。
encode()
、decode()
和 __unicode__()
由 Marc-André Lemburg 實現。內部支援使用 UCS-4 的更改由 Fredrik Lundh 和 Martin von Löwis 實現。
參見
- PEP 261 - 支援“寬”Unicode 字元
由 Paul Prescod 撰寫。
PEP 227:巢狀作用域¶
在 Python 2.1 中,靜態巢狀作用域作為可選功能新增,透過 from __future__ import nested_scopes
指令啟用。在 2.2 中,巢狀作用域不再需要特殊啟用,並且現在總是存在。本節的其餘部分是我“Python 2.1 新特性”文件中關於巢狀作用域描述的副本;如果您在 2.1 釋出時已閱讀過,則可以跳過本節的其餘部分。
Python 2.1 中引入並在 2.2 中完成的最大變化是 Python 的作用域規則。在 Python 2.0 中,任何給定時間最多有三個名稱空間用於查詢變數名:區域性、模組級和內建名稱空間。這常常令人驚訝,因為它與他們的直觀預期不符。例如,巢狀的遞迴函式定義不起作用:
def f():
...
def g(value):
...
return g(value-1) + 1
...
函式 g()
總是會引發 NameError
異常,因為名稱 g
的繫結既不在其區域性名稱空間中,也不在模組級名稱空間中。這在實踐中並沒有太大問題(你多久會這樣遞迴定義內部函式?),但這使得使用 lambda
表示式變得更加笨拙,這在實踐中是一個問題。在使用 lambda
的程式碼中,你經常會發現區域性變數透過將它們作為引數的預設值傳遞而被複制。
def find(self, name):
"Return list of any entries equal to 'name'"
L = filter(lambda x, name=name: x == name,
self.list_attribute)
return L
因此,以強函式式風格編寫的 Python 程式碼的可讀性受到極大影響。
Python 2.2 最重要的變化是語言中增加了靜態作用域來解決這個問題。首先,在上面的例子中,name=name
預設引數現在是不必要的。簡單來說,當一個給定的變數名在一個函式內部(透過賦值,或 def
、class
或 import
語句)未被賦值時,對該變數的引用將在包含作用域的區域性名稱空間中查詢。關於規則的更詳細解釋以及實現的剖析,可以在 PEP 中找到。
這種改變可能會對一些程式碼造成相容性問題,即在模組級別和函式內部(該函式包含進一步的函式定義)使用相同的變數名。然而,這種情況似乎不太可能發生,因為這樣的程式碼在第一時間閱讀起來就已經相當令人困惑了。
這一變化的一個副作用是,在某些條件下,from module import *
和 exec
語句在函式作用域內變得非法。Python 參考手冊一直指出 from module import *
僅在模組的頂層合法,但 CPython 直譯器以前從未強制執行過這一點。作為巢狀作用域實現的一部分,將 Python 原始碼轉換為位元組碼的編譯器必須生成不同的程式碼來訪問包含作用域中的變數。from module import *
和 exec
使得編譯器無法確定這一點,因為它們向區域性名稱空間添加了在編譯時不可知的名稱。因此,如果一個函式包含函式定義或帶有自由變數的 lambda
表示式,編譯器將透過引發 SyntaxError
異常來標記此問題。
為了使之前的解釋更清楚一點,這裡有一個例子:
x = 1
def f():
# The next line is a syntax error
exec 'x=2'
def g():
return x
第 4 行包含 exec
語句是一個語法錯誤,因為 exec
會定義一個名為 x
的新區域性變數,其值應該由 g()
訪問。
這不應該是一個很大的限制,因為 exec
在大多數 Python 程式碼中很少使用(而且當它被使用時,通常也意味著設計不佳)。
參見
- PEP 227 - 靜態巢狀作用域
由 Jeremy Hylton 撰寫和實現。
新增和改進的模組¶
xmlrpclib
模組由 Fredrik Lundh 貢獻給標準庫,提供了編寫 XML-RPC 客戶端的支援。XML-RPC 是一個基於 HTTP 和 XML 的簡單遠端過程呼叫協議。例如,以下程式碼片段從 O'Reilly Network 檢索 RSS 頻道列表,然後列出其中一個頻道的最新頭條:import xmlrpclib s = xmlrpclib.Server( 'http://www.oreillynet.com/meerkat/xml-rpc/server.php') channels = s.meerkat.getChannels() # channels is a list of dictionaries, like this: # [{'id': 4, 'title': 'Freshmeat Daily News'} # {'id': 190, 'title': '32Bits Online'}, # {'id': 4549, 'title': '3DGamers'}, ... ] # Get the items for one channel items = s.meerkat.getItems( {'channel': 4} ) # 'items' is another list of dictionaries, like this: # [{'link': 'http://freshmeat.net/releases/52719/', # 'description': 'A utility which converts HTML to XSL FO.', # 'title': 'html2fo 0.3 (Default)'}, ... ]
SimpleXMLRPCServer
模組使得建立簡單的 XML-RPC 伺服器變得容易。有關 XML-RPC 的更多資訊,請參閱 http://xmlrpc.scripting.com/。一些原本返回冗長元組的函式,現在返回偽序列,它們仍然像元組一樣工作,但同時具有諸如
memberst_mtime
或tm_year
等助記屬性。這些增強的函式包括stat()
、fstat()
、statvfs()
和fstatvfs()
(在os
模組中),以及localtime()
、gmtime()
和strptime()
(在time
模組中)。例如,要使用舊元組獲取檔案大小,您最終會寫出類似
file_size = os.stat(filename)[stat.ST_SIZE]
的程式碼,但現在可以更清晰地寫成file_size = os.stat(filename).st_size
。此功能的原始補丁由 Nick Mathewson 貢獻。
Python 效能分析器已進行了大量重寫,並糾正了其輸出中的各種錯誤。(由 Fred L. Drake, Jr. 和 Tim Peters 貢獻。)
socket
模組可以編譯以支援 IPv6;向 Python 的配置指令碼指定--enable-ipv6
選項。(由 Jun-ichiro “itojun” Hagino 貢獻。)在支援 C long long 型別的平臺上,
struct
模組添加了兩個新的格式字元用於 64 位整數。q
用於有符號 64 位整數,Q
用於無符號整數。返回值是 Python 的長整數型別。(由 Tim Peters 貢獻。)在直譯器的互動模式中,新增了一個內建函式
help()
,它使用 Python 2.1 中引入的pydoc
模組提供互動式幫助。help(object)
顯示關於 object 的任何可用幫助文字。help()
不帶引數時,會進入一個線上幫助工具,您可以在其中輸入函式、類或模組的名稱以閱讀其幫助文字。(由 Guido van Rossum 貢獻,使用 Ka-Ping Yee 的pydoc
模組。)對
re
模組底層 SRE 引擎進行了各種錯誤修復和效能改進。例如,re.sub()
和re.split()
函式已用 C 語言重寫。另一個貢獻的補丁將某些 Unicode 字元範圍的速度提高了兩倍,並且新增了一個finditer()
方法,該方法返回給定字串中所有不重疊匹配的迭代器。(SRE 由 Fredrik Lundh 維護。BIGCHARSET 補丁由 Martin von Löwis 貢獻。)smtplib
模組現在支援 RFC 2487,“透過 TLS 的安全 SMTP”,因此現在可以在 Python 程式和處理訊息的郵件傳輸代理之間加密 SMTP 流量。smtplib
還支援 SMTP 身份驗證。(由 Gerhard Häring 貢獻。)由 Piers Lauder 維護的
imaplib
模組支援幾個新的擴充套件:RFC 2342 中定義的 NAMESPACE 擴充套件、SORT、GETACL 和 SETACL。(由 Anthony Baxter 和 Michel Pelletier 貢獻。)rfc822
模組對電子郵件地址的解析現在符合 RFC 2822,這是對 RFC 822 的更新。(模組名稱不會更改為rfc2822
。)還添加了一個新的包email
,用於解析和生成電子郵件訊息。(由 Barry Warsaw 貢獻,源於他對 Mailman 的工作。)difflib
模組現在包含一個新的Differ
類,用於生成兩個文字行序列之間人類可讀的更改列表(一個“增量”)。還有兩個生成器函式ndiff()
和restore()
,它們分別從兩個序列返回一個增量,或從一個增量返回一個原始序列。(由 David Goodger 從 Tim Peters 的 ndiff.py 程式碼中貢獻了繁重的工作,Tim Peters 隨後進行了生成器化。)新的常量
ascii_letters
、ascii_lowercase
和ascii_uppercase
已新增到string
模組中。標準庫中有幾個模組使用string.letters
來表示 A-Za-z 範圍,但當使用 locale 時,這個假設是不正確的,因為string.letters
會根據當前 locale 定義的合法字元集而變化。所有有缺陷的模組都已修復為使用ascii_letters
。(由未知人士報告;由 Fred L. Drake, Jr. 修復。)mimetypes
模組現在透過新增一個MimeTypes
類,使得使用替代 MIME 型別資料庫變得更容易,該類接受一個要解析的檔名列表。(由 Fred L. Drake, Jr. 貢獻。)threading
模組中添加了一個Timer
類,允許安排活動在未來的某個時間發生。(由 Itamar Shtull-Trauring 貢獻。)
直譯器更改和修復¶
一些更改隻影響在 C 級別處理 Python 直譯器的人,因為他們正在編寫 Python 擴充套件模組、嵌入直譯器或只是修改直譯器本身。如果您只編寫 Python 程式碼,這裡描述的任何更改都不會對您產生太大影響。
現在可以在 C 語言中實現效能分析和跟蹤函式,它們的執行速度比基於 Python 的函式快得多,並且應該減少效能分析和跟蹤的開銷。這將引起 Python 開發環境作者的興趣。Python 的 API 中添加了兩個新的 C 函式:
PyEval_SetProfile()
和PyEval_SetTrace()
。現有的sys.setprofile()
和sys.settrace()
函式仍然存在,並且只是被更改為使用新的 C 級介面。(由 Fred L. Drake, Jr. 貢獻。)另一個低階 API,主要針對 Python 偵錯程式和開發工具的實現者,已被新增。
PyInterpreterState_Head()
和PyInterpreterState_Next()
允許呼叫者遍歷所有現有的直譯器物件;PyInterpreterState_ThreadHead()
和PyThreadState_Next()
允許遍歷給定直譯器的所有執行緒狀態。(由 David Beazley 貢獻。)垃圾回收器的 C 級介面已更改,以便更容易編寫支援垃圾回收的擴充套件型別並除錯函式濫用。各種函式的語義略有不同,因此許多函式不得不重新命名。使用舊 API 的擴充套件仍然可以編譯,但將不參與垃圾回收,因此將其更新到 2.2 應該被視為相當高的優先順序。
要將擴充套件模組升級到新的 API,請執行以下步驟:
將
Py_TPFLAGS_GC
重新命名為Py_TPFLAGS_HAVE_GC
。- 使用
PyObject_GC_New()
或PyObject_GC_NewVar()
分配 物件,並使用
PyObject_GC_Del()
釋放它們。
- 使用
將
PyObject_GC_Init()
重新命名為PyObject_GC_Track()
,將PyObject_GC_Fini()
重新命名為PyObject_GC_UnTrack()
。從物件大小計算中刪除
PyGC_HEAD_SIZE
。刪除對
PyObject_AS_GC()
和PyObject_FROM_GC()
的呼叫。PyArg_ParseTuple()
添加了一個新的et
格式序列;et
接受一個引數和一個編碼名稱,如果引數是 Unicode 字串,則將其轉換為給定編碼,如果它是 8 位字串,則保持不變,假定它已經採用所需的編碼。這與es
格式字元不同,後者假定 8 位字串採用 Python 的預設 ASCII 編碼,並將其轉換為指定的新的編碼。(由 M.-A. Lemburg 貢獻,並用於下一節中描述的 Windows 上的 MBCS 支援。)添加了一個不同的引數解析函式
PyArg_UnpackTuple()
,它更簡單且可能更快。呼叫者無需指定格式字串,只需提供預期的最小和最大引數數量,以及一組指向 PyObject* 變數的指標,這些變數將被填充引數值。方法定義表中提供了兩個新標誌
METH_NOARGS
和METH_O
,以簡化無引數方法或單個無型別引數方法的實現。呼叫此類方法比呼叫使用METH_VARARGS
的相應方法效率更高。此外,編寫 C 方法的舊METH_OLDARGS
樣式現已正式棄用。添加了兩個新的包裝函式
PyOS_snprintf()
和PyOS_vsnprintf()
,以提供相對較新的snprintf()
和vsnprintf()
C 庫 API 的跨平臺實現。與標準的sprintf()
和vsprintf()
函式相比,Python 版本會檢查用於防止緩衝區溢位的緩衝區邊界。(由 M.-A. Lemburg 貢獻。)_PyTuple_Resize()
函式已失去一個未使用的引數,因此現在它接受 2 個引數而不是 3 個。第三個引數從未被使用,在將程式碼從早期版本移植到 Python 2.2 時可以簡單地丟棄。
其他更改和修復¶
像往常一樣,整個原始碼樹中散佈著許多其他改進和錯誤修復。搜尋 CVS 更改日誌發現,在 Python 2.1 和 2.2 之間應用了 527 個補丁並修復了 683 個錯誤;2.2.1 應用了 139 個補丁並修復了 143 個錯誤;2.2.2 應用了 106 個補丁並修復了 82 個錯誤。這些數字可能被低估了。
一些更顯著的更改是
由 Jack Jansen 維護的 Python MacOS 移植程式碼現在儲存在主要的 Python CVS 樹中,並且為了支援 MacOS X 進行了許多更改。
最重要的變化是能夠將 Python 構建為一個框架,透過在編譯 Python 時向 configure 指令碼提供
--enable-framework
選項來實現。根據 Jack Jansen 的說法,“這會將一個自包含的 Python 安裝以及 OS X 框架‘粘合’安裝到/Library/Frameworks/Python.framework
(或選擇的其他位置)。目前,這樣做並沒有多少直接的額外好處(實際上,有一個缺點是您必須更改 PATH 才能找到 Python),但它是建立完整 Python 應用程式、移植 MacPython IDE、可能將 Python 用作標準 OSA 指令碼語言等的基礎。”MacPython 工具箱模組的大部分,它們與 MacOS API(如視窗、QuickTime、指令碼等)介面,已經移植到 OS X,但它們在
setup.py
中被註釋掉了。希望試驗這些模組的人可以手動取消註釋。傳遞給不接受它們的內建函式的關鍵字引數現在會導致引發
TypeError
異常,並顯示訊息“function takes no keyword arguments”。弱引用,在 Python 2.1 中作為擴充套件模組新增,現在是核心的一部分,因為它們用於實現新式類。因此,
ReferenceError
異常已從weakref
模組移出,成為內建異常。Tim Peters 編寫的新指令碼
Tools/scripts/cleanfuture.py
會自動從 Python 原始碼中刪除過時的__future__
語句。內建函式
compile()
增加了一個額外的 flags 引數,因此現在可以在模擬 shell(例如 IDLE 和其他開發環境提供的 shell)中正確觀察__future__
語句的行為。這在 PEP 264 中有描述。(由 Michael Hudson 貢獻。)Python 1.6 引入的新許可證與 GPL 不相容。透過對 2.2 許可證進行一些小的文字更改,此問題已修復,因此現在再次合法地將 Python 嵌入到 GPL 程式中。請注意,Python 本身並非 GPL 許可證,而是採用一種與 BSD 許可證基本相同的許可證,就像它一直以來那樣。許可證更改也應用於 Python 2.0.1 和 2.1.1 版本。
當在 Windows 上遇到 Unicode 檔名時,Python 現在會將其轉換為 MBCS 編碼的字串,供 Microsoft 檔案 API 使用。由於檔案 API 明確使用 MBCS,Python 選擇 ASCII 作為預設編碼被證明是一個麻煩。在 Unix 上,如果
locale.nl_langinfo(CODESET)
可用,則使用 locale 的字元集。(Windows 支援由 Mark Hammond 在 Marc-André Lemburg 的協助下貢獻。Unix 支援由 Martin von Löwis 新增。)Windows 上現在支援大檔案。(由 Tim Peters 貢獻。)
Tools/scripts/ftpmirror.py
指令碼現在會解析一個.netrc
檔案(如果您有的話)。(由 Mike Romberg 貢獻。)xrange()
函式返回的物件的某些特性現在已被棄用,並在訪問時觸發警告;它們將在 Python 2.3 中消失。xrange
物件試圖透過支援切片、序列乘法和in
運算子來偽裝成完整的序列型別,但這些特性很少使用且存在錯誤。tolist()
方法以及start
、stop
和step
屬性也已被棄用。在 C 級別,PyRange_New()
函式的第四個引數repeat
也已被棄用。字典實現中有許多補丁,主要用於修復當字典包含偷偷更改其雜湊值或更改其所包含的字典的物件時可能導致的核心轉儲。有一段時間,python-dev 陷入了一種溫和的節奏:Michael Hudson 發現一個導致核心轉儲的情況,Tim Peters 修復了錯誤,Michael 又發現另一個情況,如此迴圈往復。
在 Windows 上,Python 現在可以使用 Borland C 編譯,這得益於 Stephen Hansen 貢獻的許多補丁,儘管結果尚未完全功能化。(但這確實是進步……)
另一個 Windows 增強:Wise Solutions 大方地向 PythonLabs 提供了他們的 InstallerMaster 8.1 系統。早期的 PythonLabs Windows 安裝程式使用 Wise 5.0a,該版本開始顯示其年代感。(由 Tim Peters 打包。)
以
.pyw
結尾的檔案現在可以在 Windows 上匯入。.pyw
是 Windows 專屬的,用於指示指令碼需要使用 PYTHONW.EXE 而不是 PYTHON.EXE 執行,以防止出現 DOS 控制檯顯示輸出。此補丁使得匯入此類指令碼成為可能,以防它們也可用作模組。(由 David Bolen 實現。)在 Python 使用 C
dlopen()
函式載入擴充套件模組的平臺上,現在可以使用sys.getdlopenflags()
和sys.setdlopenflags()
函式來設定dlopen()
使用的標誌。(由 Bram Stolk 貢獻。)內建函式
pow()
不再支援在提供浮點數時使用 3 個引數。pow(x, y, z)
返回(x**y) % z
,但這對於浮點數從未有用,並且最終結果會根據平臺而不可預測地變化。像pow(2.0, 8.0, 7.0)
這樣的呼叫現在會引發TypeError
異常。
致謝¶
作者要感謝以下人員為此文的各個草稿提供了建議、更正和幫助:Fred Bremmer, Keith Briggs, Andrew Dalke, Fred L. Drake, Jr., Carel Fellinger, David Goodger, Mark Hammond, Stephen Hansen, Michael Hudson, Jack Jansen, Marc-André Lemburg, Martin von Löwis, Fredrik Lundh, Michael McLay, Nick Mathewson, Paul Moore, Gustavo Niemeyer, Don O’Donnell, Joonas Paalasma, Tim Peters, Jens Quade, Tom Reinhardt, Neil Schemenauer, Guido van Rossum, Greg Ward, Edward Welbourne。