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_funcencode_func 相反,它接受 8 位字串並返回 2 元組 (ustring, length),包括生成的 Unicode 字串 ustring 和整數 length,表示消耗了多少 8 位字串。

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

  • 類似地,stream_writer 是一個支援將輸出編碼到流的類。stream_writer(file_obj) 返回一個支援 write()writelines() 方法的物件。這些方法期望 Unicode 字串,並在輸出時將其轉換為給定的編碼。

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

import codecs

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

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

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

以下程式碼將從檔案中讀取 UTF-8 輸入

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

可透過 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 namefrom 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 異常。 UnboundLocalErrorNameError 的子類,因此任何期望引發 NameError 的現有程式碼都應該仍然有效。

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

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

內建函式的更改

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

當第一個引數是字串時,int()long() 函式現在接受可選的“base”引數。 int('123', 10) 返回 123,而 int('123', 16) 返回 291。 int(123, 16) 會引發 TypeError 異常,並顯示訊息“can't convert non-string with explicit base”。

sys 模組中添加了一個新變數,用於儲存更詳細的版本資訊。 sys.version_info 是一個元組 (major, minor, micro, level, serial)。 例如,在假設的 2.0.1beta1 中,sys.version_info 將是 (2, 0, 1, 'beta', 1)level 是一個字串,例如 "alpha""beta""final" 用於最終發行版。

字典有一個奇怪的新方法,setdefault(key, default),它的行為與現有的 get() 方法類似。但是,如果缺少鍵,setdefault() 既會像 get() 一樣返回 default 的值,也會將其作為 key 的值插入到字典中。因此,以下幾行程式碼

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

可以簡化為單個 return dict.setdefault(key, []) 語句。

直譯器設定了最大遞迴深度,以便在 C 堆疊填滿並導致核心轉儲或 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_exsocket.bind 也類似地比較寬鬆。2.0alpha1 收緊了這些函式,但是由於文件實際上使用了錯誤的多個引數形式,許多人編寫的程式碼會在更嚴格的檢查下崩潰。面對公眾的反應,GvR 撤銷了這些更改,因此對於 socket 模組,文件已修復,並且多個引數形式僅被標記為已棄用;在未來的 Python 版本中,它將會再次收緊。

字串字面量中的 \x 轉義現在需要正好 2 個十六進位制數字。以前,它會消耗 ‘x’ 之後的所有十六進位制數字,並取結果的最低 8 位,因此 \x123456 等同於 \x56

AttributeErrorNameError 異常具有更友好的錯誤訊息,其文字類似於 'Spam' instance has no attribute 'eggs'name 'eggs' is not defined。以前,錯誤訊息只是缺失的屬性名稱 eggs,並且利用此事實編寫的程式碼將在 2.0 中崩潰。

為了使整數和長整數更具互換性,已經做了一些工作。在 1.5.2 中,為 Solaris 添加了大型檔案支援,以允許讀取大於 2 GiB 的檔案;這使得檔案物件的 tell() 方法返回長整數而不是常規整數。一些程式碼會減去兩個檔案偏移量,並嘗試使用結果來乘以序列或切片字串,但這會引發 TypeError。在 2.0 中,長整數可以用於乘以或切片序列,並且它的行為正如你直觀期望的那樣;3L * 'abc' 產生 ‘abcabcabc’,而 (0,1,2,3)[2L:4L] 產生 (2,3)。長整數也可以用於各種以前只接受整數的上下文中,例如檔案物件的 seek() 方法,以及 % 運算子(%d%i%x 等)支援的格式。例如,"%d" % 2L**64 將產生字串 18446744073709551616

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

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

-X 命令列選項(它將所有標準異常轉換為字串而不是類)已被刪除;標準異常現在將始終是類。包含標準異常的 exceptions 模組已從 Python 轉換為內建 C 模組,由 Barry Warsaw 和 Fredrik Lundh 編寫。

擴充套件/嵌入更改

其中一些更改是隱藏的,並且只有編寫 C 擴充套件模組或將 Python 直譯器嵌入到更大的應用程式中的人員才會注意到。如果您不處理 Python 的 C API,則可以安全地跳過本節。

Python C API 的版本號已遞增,因此必須重新編譯為 1.5.2 編譯的 C 擴展才能與 2.0 一起使用。在 Windows 上,由於 Windows DLL 的工作方式,Python 2.0 無法匯入為 Python 1.5.x 構建的第三方擴充套件,因此 Python 會引發異常並且匯入將失敗。

Jim Fulton 的 ExtensionClass 模組的使用者會很高興地發現,已添加了鉤子,以便 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.hInclude/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 類,例如 ElementText,是 Node 基類的子類。因此,DOM 樹中的所有節點都支援某些通用方法,例如 toxml(),它返回一個包含節點及其子節點的 XML 表示形式的字串。每個類也有自己的特殊方法;例如,ElementDocument 例項有一個方法來查詢所有具有給定標記名稱的子元素。繼續前面的兩行示例

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 的擴充套件標準庫進行了許多改進和錯誤修復;一些受影響的模組包括 readlineConfigParsercgicalendarposixreadlinexmllibaifcchunkwaverandomshelvenntplib。請查閱 CVS 日誌以獲取逐個補丁的詳細資訊。

Brian Gallew 為 socket 模組貢獻了 OpenSSL 支援。OpenSSL 是安全套接字層的實現,它對透過套接字傳送的資料進行加密。在編譯 Python 時,您可以編輯 Modules/Setup 以包含 SSL 支援,這會在 socket 模組中新增一個額外的函式:socket.ssl(socket, keyfile, certfile),它接受一個套接字物件並返回一個 SSL 套接字。httpliburllib 模組也進行了更改以支援 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_linecreate_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 貢獻。)

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

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

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

  • linuxaudiodev:支援 Linux 上的 /dev/audio 裝置,與現有的 sunaudiodev 模組類似。(由 Peter Bosch 貢獻,並由 Jeremy Hylton 修復。)

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

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

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

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

  • UserString:一個有用的基類,用於派生行為類似於字串的物件。

  • webbrowser: 一個模組,提供了一種平臺無關的方式來在特定 URL 上啟動 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。