Python 2.2 新特性

作者:

A.M. Kuchling

簡介

本文介紹了 2002 年 10 月 14 日釋出的 Python 2.2.2 的新特性。Python 2.2.2 是 Python 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 版本中可用的例項方法之外,現在還可以定義靜態方法和類方法。

  • 還可以透過使用稱為屬性的新機制,在訪問或設定例項屬性時自動呼叫方法。許多對__getattr__()的使用都可以重寫為使用屬性,從而使生成的程式碼更簡單,速度更快。作為一個小小的額外好處,屬性現在也可以有文件字串。

  • 可以使用將例項的合法屬性列表限制為特定集合,從而可以防止拼寫錯誤,並可能在未來的 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)刪除objectvalue屬性。

例如,當您編寫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 fdefstatic 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 或其他內容,但大多數使用者只需在生成的庫之上編寫程式碼,而忽略實現細節。

多重繼承:菱形規則

多重繼承也透過更改名稱解析規則而變得更加有用。考慮以下類集合(圖表取自 PEP 253,作者 Guido van Rossum)

      class A:
        ^ ^  def save(self): ...
       /   \
      /     \
     /       \
    /         \
class B     class C:
    ^         ^  def save(self): ...
     \       /
      \     /
       \   /
        \ /
      class D

經典類的查詢規則很簡單,但不是很智慧;它會從左到右,以深度優先的方式搜尋基類。對 D.save() 的引用將搜尋類 DB,然後是 A,並在其中找到並返回 save()C.save() 根本不會被找到。這很糟糕,因為如果 Csave() 方法正在儲存特定於 C 的一些內部狀態,那麼不呼叫它會導致該狀態永遠無法儲存。

新式類遵循不同的演算法,該演算法解釋起來有點複雜,但在此情況下會執行正確的操作。(請注意,Python 2.3 將此演算法更改為在大多數情況下產生相同結果的演算法,但對於非常複雜的繼承圖會產生更有用的結果。)

  1. 按照經典的查詢規則列出所有基類,如果多次訪問某個類,則將其多次包含在列表中。在上面的示例中,訪問的類列表是 [DBACA]。

  2. 掃描列表以查詢重複的類。如果找到任何重複項,則刪除除一個以外的所有重複項,保留列表中最後一個重複項。在上面的示例中,刪除重複項後,列表變為 [DBCA]。

按照此規則,引用 D.save() 將返回 C.save(),這正是我們想要的行為。此查詢規則與 Common Lisp 遵循的規則相同。一個新的內建函式 super() 提供了一種獲取類的超類的方法,而無需重新實現 Python 的演算法。最常用的形式將是 super(class, obj),它返回一個繫結的超類物件(而不是實際的類物件)。這種形式將在方法中使用,以呼叫超類中的方法;例如,Dsave() 方法將如下所示:

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__(),而只有在例項的字典中找不到 foo 時才會呼叫舊的 __getattr__()

然而,Python 2.2 對屬性的支援通常是捕獲屬性引用的更簡單方法。編寫 __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")

這肯定比一對檢查 size 屬性並對其進行特殊處理,同時從例項的 __dict__ 中檢索所有其他屬性的 __getattr__() / __setattr__() 方法更清晰,更容易編寫。對 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 級別的迭代介面。物件可以定義呼叫者如何迴圈訪問它們。

在 2.1 之前的 Python 版本中,使 for item in obj 工作的通常方法是定義一個類似這樣的 __getitem__() 方法

def __getitem__(self, index):
    return <next item>

__getitem__() 更恰當地用於定義物件的索引操作,以便您可以編寫 obj[5] 來檢索第六個元素。當您僅使用它來支援 for 迴圈時,這有點誤導。考慮一個想要被迴圈訪問的檔案類物件;index 引數本質上是無意義的,因為該類可能假設一系列的 __getitem__() 呼叫每次都會使 index 遞增一。換句話說,__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 語句。yieldreturn 語句的最大區別在於,當到達 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 皇后問題(在 $N\times N$ 棋盤上放置 $N$ 個皇后,使任何一個皇后都不會威脅到另一個皇后)和騎士巡邏問題(騎士在 $N\times N$ 棋盤上不重複訪問任何方格的路徑)生成解決方案。

生成器的概念來自其他程式語言,特別是 Icon (https://www2.cs.arizona.edu/icon/),其中生成器的概念是核心。在 Icon 中,每個表示式和函式呼叫都像一個生成器。以下來自 https://www2.cs.arizona.edu/icon/docs/ipd266.htm 的“Icon 程式語言概述”中的一個示例給出了它的外觀

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 核心語言的一個新部分,但是學習或使用它們不是強制性的;如果它們沒有解決你遇到的任何問題,可以隨意忽略它們。與 Icon 相比,Python 的介面的一個新穎之處在於,生成器的狀態被表示為一個具體的物件(迭代器),該物件可以傳遞給其他函式或儲存在資料結構中。

另請參閱

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 異常,並顯示訊息 ‘切片索引必須是 int’。

Python 2.2 將根據需要將值從短整數轉換為長整數。不再需要 'L' 字尾來表示長整數字面量,因為現在編譯器將選擇適當的型別。(在未來 2.x 版本的 Python 中將不鼓勵使用 '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 也可以透過向 configure 指令碼提供 --enable-unicode=ucs4 來編譯為使用 UCS-4(32 位無符號整數)作為其內部編碼。(也可以指定 --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 預設引數現在是不必要的。簡而言之,當給定的變數名未在函式內賦值(透過賦值,或 defclassimport 語句)時,對該變數的引用將在封閉作用域的區域性名稱空間中查詢。有關規則的更詳細解釋以及實現的剖析,請參見 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

包含 exec 語句的第 4 行是一個語法錯誤,因為 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/

  • 新的 hmac 模組實現了 RFC 2104 中描述的 HMAC 演算法。(由 Gerhard Häring 貢獻。)

  • 一些最初返回長元組的函式現在返回偽序列,這些序列仍然像元組一樣工作,但也有助記屬性,例如 memberst_mtimetm_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 用於無符號的 64 位整數。該值以 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 程式碼,然後進行了生成器化。)

  • 新的常量 ascii_lettersascii_lowercaseascii_uppercase 已新增到 string 模組。標準庫中有幾個模組使用 string.letters 來表示 A-Za-z 範圍,但當使用區域設定時,這種假設是不正確的,因為 string.letters 會根據當前區域設定定義的合法字元集而變化。所有有問題的模組都已修復為使用 ascii_letters。(由一位不知名人士報告;由 Fred L. Drake, Jr. 修復。)

  • 透過新增一個 MimeTypes 類,mimetypes 模組現在可以更輕鬆地使用備用 MIME 型別資料庫,該類接受要解析的檔名列表。(由 Fred L. Drake, Jr. 貢獻。)

  • 一個 Timer 類已新增到 threading 模組,允許安排在未來某個時間發生活動。(由 Itamar Shtull-Trauring 貢獻。)

直譯器更改和修復

其中一些更改隻影響在 C 級別處理 Python 直譯器的人員,因為他們正在編寫 Python 擴充套件模組、嵌入直譯器或僅僅是在 hack 直譯器本身。 如果你只編寫 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_NOARGSMETH_O,以簡化沒有引數或只有一個未型別化引數的方法的實現。 呼叫此類方法比呼叫使用 METH_VARARGS 的相應方法更有效。 此外,舊的編寫 C 方法的 METH_OLDARGS 樣式現在已正式棄用。

  • 添加了兩個新的包裝函式 PyOS_snprintf()PyOS_vsnprintf(),為相對較新的 snprintf()vsnprintf() C lib 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 工具箱模組(與視窗、QuickTime、指令碼等 MacOS API 介面)都已移植到 OS X,但它們在 setup.py 中被註釋掉了。 想要嘗試這些模組的人可以手動取消註釋它們。

  • 傳遞給不接受關鍵字引數的內建函式的關鍵字引數現在會導致引發 TypeError 異常,並顯示訊息“函式不接受關鍵字引數”。

  • 在 Python 2.1 中作為擴充套件模組新增的弱引用現在是核心的一部分,因為它們用於新式類的實現中。 因此,ReferenceError 異常已從 weakref 模組移動到成為內建異常。

  • Tim Peters 的新指令碼 Tools/scripts/cleanfuture.py 會自動從 Python 原始碼中刪除過時的 __future__ 語句。

  • 已向內建函式 compile() 添加了一個額外的 flags 引數,因此現在可以在模擬 shell 中正確觀察 __future__ 語句的行為,例如 IDLE 和其他開發環境提供的 shell。 這在 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) 可用,則會使用區域設定的字元集。(Windows 支援由 Mark Hammond 貢獻,並得到了 Marc-André Lemburg 的協助。Unix 支援由 Martin von Löwis 新增。)

  • 現在 Windows 上啟用了大檔案支援。(由 Tim Peters 貢獻。)

  • 如果存在 .netrc 檔案,Tools/scripts/ftpmirror.py 指令碼現在會解析該檔案。(由 Mike Romberg 貢獻。)

  • xrange() 函式返回的物件的某些特性現在已被棄用,並在訪問時觸發警告;它們將在 Python 2.3 中消失。xrange 物件試圖透過支援切片、序列乘法和 in 運算子來偽裝成完整的序列型別,但這些特性很少被使用,因此存在缺陷。tolist() 方法和 startstopstep 屬性也被棄用。在 C 級別,PyRange_New() 函式的第四個引數 repeat 也已被棄用。

  • 字典的實現有很多補丁,主要是為了修復當字典包含秘密更改了雜湊值或改變了它們所包含的字典的物件時可能出現的崩潰。一段時間內,python-dev 陷入了這樣一種溫和的節奏:Michael Hudson 發現一個導致崩潰的案例,Tim Peters 修復這個 bug,Michael 又發現另一個案例,如此迴圈往復。

  • 在 Windows 上,得益於 Stephen Hansen 貢獻的一些補丁,Python 現在可以使用 Borland C 進行編譯,儘管結果還不完全可用。(但這確實是進步……)

  • 另一個 Windows 增強功能:Wise Solutions 大方地為 PythonLabs 提供了他們的 InstallerMaster 8.1 系統。早期的 PythonLabs Windows 安裝程式使用了 Wise 5.0a,它開始顯得過時。(由 Tim Peters 打包。)

  • 現在可以在 Windows 上匯入以 .pyw 結尾的檔案。.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。