Python 版本 3,也被稱為 Python 3000 或 Py3K(仿效 Microsoft Windows 2000 作業系統而命名的暱稱)是 Guido van Rossum 通用程式設計語言的最新版本。雖然新版本對該核心語言做了很多改進,但還是打破了與 2.x 版本的向後相容性。其他一些變化則是人們期待已久的,比如:
- 真正的除法 — 例如,1/2 返回的是 .5。
long
和 int
類型被統一為一種類型,刪除了尾碼 L。
True
、False
和 None
現在都是關鍵字。
本文 — Python 3 系列文章中的第一篇 — 的內容涵蓋了新的 print()
函數、input()
、輸入/輸出(I/O)的變化、新的 bytes
資料類型、字串和字串格式化的變化以及內建的 dict
類型的變化。本文面向的是那些熟悉 Python 並對新版本的變化高度興趣但又不想費力讀完所有 Python Enhancement Proposal(PEP)的編程人員。
如今,您將需要讓手指習慣於鍵入 print("hello")
,而不是原來的 print "hello"
,這是因為 print
現在是一個函數,不再是一個語句。我知道,這多少有點痛苦。我認識的每個 Python 程式員 — 一旦安裝了版本 3 並得到 “文法不正確” 錯誤 — 都會鬱悶地大叫。我知道這兩個額外的符號十分討厭;我也知道這將會破壞向後相容性。但是這種改變還是有好處的。
讓我們考慮這樣的情況,即需要將標準輸出(stdout)重新導向到一個日誌。如下的例子會開啟檔案 log.txt 以便進行追加並將對象指定給 fid
。之後,利用 print>>
將一個字串重新導向給檔案 fid
:
>>>fid = open("log.txt", "a")>>>print>>fid, "log text" |
另外一個例子是重新導向給標準錯誤(sys.stderr):
>>>print>>sys.stderr, "an error occurred" |
上述兩個例子都不錯,但還有更好的解決方案。新的文法只要求給 print()
函數的關鍵字參數 file
傳遞一個值就可以了。比如:
>>>fid = open("log.txt", "a")>>>print("log.txt", file=fid) |
這樣的代碼,文法更為清晰。另一個好處是通過向 sep
關鍵字參數傳遞一個字串就能更改分割符(separator),通過向 end
關鍵字參數傳遞另外一個字串就能更改結束字串。要更改分割符,可以利用:
>>>print("Foo", "Bar", sep="%")>>>Foo%Bar |
總地來說,新的文法為:
print([object, ...][, sep=' '][, end='endline_character_here'][, file=redirect_to_here]) |
其中,方括弧([]
)內的代碼是可選的。預設地,若只調用 print()
自身,結果會追加一個分行符號( \n
)。
從 raw_input() 到 input()
在 Python 版本 2.x 中,raw_input()
會從標準輸入(sys.stdin)讀取一個輸入並返回一個字串,且尾部的分行符號從末尾移除。下面的這個例子使用 raw_input()
從命令提示字元擷取一個字串,然後將值賦給 quest
。
>>>quest = raw_input("What is your quest? ")What is your quest? To seek the holy grail.>>>quest'To seek the holy grail.' |
與之不同,Python 2.x 中的 input()
函數需要的是一個有效 Python 運算式,比如 3+5。
最初,曾有人建議將 input()
和 raw_input()
從 Python 內建的名稱空間一併刪除,因此就需要進行匯入來獲得輸入能力。這從方法上就不對;因為,簡單鍵入:
>>>quest = input("What is your quest?") |
將會變為:
>>>import sys>>>print("What is your quest?")>>>quest = sys.stdin.readline() |
對於一個簡單輸入而言,這太過繁瑣,並且對於一個新手,這未免太難理解。往往需要向他們講述模組 和匯入 究竟是怎麼回事、字串輸出以及句點操作符又是如何工作的(如此麻煩的話,與 Java 語言就沒什麼差別了)。所以,在 Python 3 內,將 raw_input()
重新命名為 input()
,這樣一來,無須匯入也能從標準輸入獲得資料了。如果您需要保留版本 2.x 的 input()
功能,可以使用 eval(input())
,效果基本相同。
有關 bytes 的簡介
新的資料類型 bytes literal 及 bytes
對象的用途是儲存位元據。此對象是 0 到 127 的不可修改的整數序列或純粹的 ASCII 字元。實際上,它是版本 2.5 中 bytearray
對象的不可修改版本。一個 bytes literal 是一個前面冠以 b 的字串 — 例如,b'byte literal'。對 bytes literal 的計算會產生一個新的 bytes
對象。可以用 bytes()
函數建立一個新的 bytes
對象。bytes
對象的建構函式為:
bytes([initializer[, encoding]]) |
例如:
>>>b = (b'\xc3\x9f\x65\x74\x61')>>>print(b)b'\xc3\x83\xc2\x9feta' |
會建立一個 bytes
對象,但這是多餘的,因為通過賦值一個 byte literal 就完全可以建立 bytes
對象。(我只是想要說明這麼做是可行的,但是我並不建議您這麼做。)如果您想要使用 iso-8859-1 編碼,可以嘗試下面的做法:
>>>b = bytes('\xc3\x9f\x65\x74\x61', 'iso-8859-1')>>>print(b)b'\xc3\x83\xc2\x9feta' |
如果初始化器(initializer)是一個字串,那麼就必須提供一種編碼。如果初始化器是一個 bytes literal,則無須指定編碼類別型:請記住,bytes literal 並不是字串。但是與字串相似,可以串連多個位元組:
>>>b'hello' b' world'b'hello world' |
用 bytes()
方法代表位元據以及被編碼的文本。要將 bytes
轉變為 str
, bytes
對象必須要進行解碼(稍後會詳細介紹)。位元據用 decode()
方法編碼。例如:
>>>b'\xc3\x9f\x65\x74\x61'.decode()'ßeta' |
也可以從檔案中直接讀取位元據。請看以下的代碼:
>>>data = open('dat.txt', 'rb').read() >>>print(data) # data is a string>>># content of data.txt printed out here |
它的功能是開啟檔案以便在二進位模式內讀取一個檔案對象,並在整個檔案內進行讀取。
字串
Python 具有單一的字串類型 str
,其功能類似於版本 2.x 的 unicode 類型。換言之,所有字串都是 unicode 字串。而且 — 對非拉丁文的文本使用者也非常方便 — 非-ASCII 標識符現在也是允許的。例如:
>>>césar = ["author", "consultant"]>>>print(césar)['author', 'consultant'] |
在 Python 之前的版本內,repr()
方法會將 8-位字串轉變為 ASCII。例如:
>>>repr('é')"'\\xc3\\xa9'" |
現在,它會返回一個 unicode 字串:
正如我之前提到的,這個字串是內建的字串類型。
字串對象和位元組對象是不相容的。如果想要得到位元組的字串表示,需要使用它的 decode()
方法。相反,如果想要從該字串得到 bytes literal 表示,可以使用字串對象的 encode()
方法。
字串格式化方面的變化
很多 Python 程式員都感覺用來格式化字串的這個內建的 %
操作符太有限了,這是因為:
- 它是一個二進位的操作符,最多隻能接受兩個參數。
- 除了格式化字串參數,所有其他的參數都必須用一個元組(tuple)或是一個字典(dictionary)進行擠壓。
這種格式化多少有些不靈活,所以 Python 3 引入了一種新的進行字串格式化的方式(版本 3 保留了 %
操作符和 string.Template
模組)。字串對象現在均具有一個方法 format()
,此方法接受位置參數和關鍵字參數,二者均傳遞到 replacement 欄位 。Replacement 欄位在字串內由花括弧({}
)標示。replacement 欄位內的元素被簡單稱為一個欄位。以下是一個簡單的例子:
>>>"I love {0}, {1}, and {2}".format("eggs", "bacon", "sausage")'I love eggs, bacon, and sausage' |
欄位 {0}、{1} 和 {2} 通過位置參數 eggs
、 bacon
和 sausage
被傳遞給 format()
方法。如下的例子顯示了如何使用 format()
通過關鍵字參數的傳遞來進行格式化:
>>>"I love {a}, {b}, and {c}".format(a="eggs", b="bacon", c="sausage")'I love eggs, bacon, and sausage' |
下面是另外一個綜合了位置參數和關鍵字參數的例子:
>>>"I love {0}, {1}, and {param}".format("eggs", "bacon", param="sausage")'I love eggs, bacon, and sausage' |
請記住,在關鍵字參數之後放置非關鍵字參數是一種語法錯誤。要想轉義花括弧,只需使用雙倍的花括弧,如下所示:
>>>"{{0}}".format("can't see me")'{0}' |
位置參數 can't see me
沒有被輸出,這是因為沒有欄位可以輸出。請注意這不會產生錯誤。
新的 format()
內建函數可以格式化單個值。比如:
>>>print(format(10.0, "7.3g")) 10 |
換言之,g 代表的是 一般格式,它輸出的是寬度固定的值。小數點前的第一個數值指定的是最小寬度,小數點後的數值指定的是精度。format specifier 的完整文法超出了本文的討論範圍,更多資訊。
內建 dict 類型的變化
3.0 內的另一個重大改變是字典內 dict.iterkeys()
、 dict.itervalues()
和 dict.iteritems()
方法的刪除。取而代之的是 .keys()
、 .values()
和 .items()
,它們被進行了修補,可以返回輕量的、類似於集的容器物件,而不是鍵和值的列表。這樣的好處是在不進行鍵和條目複製的情況下,就能在其上執行 set
操作。例如:
>>>d = {1:"dead", 2:"parrot"}>>>print(d.items())<built-in method items of dict object at 0xb7c2468c> |
注意:在 Python 內,集 是惟一元素的無序集合。
這裡,我建立了具有兩個鍵和值的一個字典,然後輸出了 d.items()
的值,返回的是一個對象,而不是值的列表。可以像 set
對象那樣測試某個元素的成員資格,比如:
>>>1 in d # test for membershipTrue |
如下是在 dict_values
對象的條目上進行迭代的例子:
>>>for values in d.items():... print(values) ...deadparrot |
不過,如果您的確想要得到值的列表,可以對所返回的 dict
對象進行強制類型轉換。比如:
>>>keys = list(d.keys())>>>print(keys)[1,2] |
回頁首
新的 I/O
元類
Wikipedia 對元類的定義是這樣的,“一個元類 是這樣一個類,其執行個體也是類。” 在本系列的第 2 部分我會對這個概念進行詳細的介紹。
在深入研究 I/O 的新機制之前,很有必要先來看看抽象基類( abstract base classes,ABC)。更深入的介紹將會在本系列的第 2 部分提供。
ABC 是一些無法被執行個體化的類。要使用 ABC,子類必須繼承自此 ABC 並且還要覆蓋其抽象方法。如果方法的首碼使用 @abstractmethod
修飾符(decorator),那麼此方法就是一個抽象方法。新的 ABC 架構還提供了 @abstractproperty
修飾符以便定義抽象屬性。可以通過匯入標準庫模組 abc
來訪問這個新架構。清單 1 所示的是一個簡單的例子。
清單 1. 一個簡單的抽象基類
from abc import ABCMetaclass SimpleAbstractClass(metaclass=ABCMeta): passSimpleAbstractClass.register(list)assert isinstance([], SimpleAbstractClass) |
register()
方法調用接受一個類作為其參數並會讓此 ABC 成為所註冊類的子類。這一點可以通過在最後一行上調用 assert
語句進行驗證。清單 2 是使用修飾符的另外一個例子。
清單 2. 使用修飾符的一個抽象基類
from abc import ABCMeta, abstractmethodclass abstract(metaclass=ABCMeta): @abstractmethod def absMeth(self): pass class A(abstract): # must implement abstract method def absMeth(self): return 0 |
瞭解了 ABC 之後,我們就可以繼續探究新的 I/O 系統了。之前的 Python 發布版都缺少一些重要但是出色的函數,比如用於類似於流的對象的 seek()
。 類似於流的對象 是一些具有 read()
和 write()
方法的類似於檔案的對象 — 比如,socket 或檔案。Python 3 具有很多針對類似於流的對象的 I/O 層 — 一個原始的 I/O 層、一個被緩衝的 I/O 層以及一個文本 I/O 層 — 每層均由其自身的 ABC 及實現定義。
開啟一個流還是需要使用內建的 open(fileName)
函數,但是也可以調用 io.open(fileName))
。這麼做會返回一個緩衝了的文字檔;read()
和 readline()
會返回字串(請注意,Python 3 內的所有字串都是 unicode)。您也可以使用 open(fileName, 'b')
開啟一個緩衝了的二進位檔案。在這種情況下,read()
會返回位元組,但 readline()
則不能用。
此內建 open()
函數的建構函式是:
open(file,mode="r",buffering=None,encoding=None,errors=None,newline=None,closefd=True) |
可能的模式有:
r
:讀
w
:開啟供寫入
a
:開啟供追加
b
:二進位模式
t
:文字模式
+
:開啟一個磁碟檔案供更新
U
:通用換行模式
預設的模式是 rt
,即開啟供讀取的文字模式。
buffering
關鍵字參數的期望值是以下三個整數中的一個以決定緩衝策略:
0
:關閉緩衝
1
:行緩衝
> 1
:完全緩衝(預設)
預設的編碼方式獨立於平台。關閉檔案描述符或 closefd
可以是 True 或 False。如果是 False,此檔案描述符會在檔案關閉後保留。若檔案名稱無法奏效的話,那麼 closefd
必須設為 True。
open()
返回的對象取決於您所設定的模式。表 1 給出了傳回型別。表 1. 針對不同開啟模式的傳回型別
模式 |
返回對象 |
文字模式 |
TextIOWrapper |
二進位 |
BufferedReader |
寫二進位 |
BufferedWriter |
追加二進位 |
BufferedWriter |
讀/寫入模式 |
BufferedRandom |
請注意:文字模式可以是 w
、 r
、wt
、 rt
等。
清單 3 中所示的例子開啟的是一個緩衝了的二進位流以供讀取。
清單 3. 開啟一個緩衝了的二進位流以供讀取
>>>import io>>>f = io.open("hashlib.pyo", "rb") # open for reading in binary mode>>>f # f is a BufferedReader object <io.BufferedReader object at 0xb7c2534c>>>>f.close() # close stream |
BufferedReader
對象可以訪問很多有用的方法,比如 isatty
、 peek
、raw
、 readinto
、readline
、 readlines
、seek
、 seekable
、tell
、 writable
、write
和 writelines
。要想查看完整列表,可以在 BufferedReader
對象上運行 dir()
。
結束語
Python 社區是否會接??版本 3 還尚在人們的猜測之中。打破向後相容性意味著將要為兩種版本提供支援。一些項目開發人員可能不太想遷移其項目,即便是使用版本 2 到 3 的轉化器。就我個人而言,我發現從 Python 版本 2 遷移到 3 其實不過是對幾個事情的重新認識:它當然不會像從 Python 遷移到 Java 或 Perl 語言那樣變化強烈。很多變化是早就在人們意料中的,比如對 dict
的實質更改。執行 print()
遠比執行 Java 的 System.out.println()
容易得多,學習起來也相對容易,所以的確能帶來一些好處。
我猜想,blogosphere 內的一些文章會讓 Python 的支援者也會誤認為其中的某些變更 — 例如對向後相容性的打破 — 具有破壞性的影響。 Lambda 本來就是準備好要刪除的,只不過一直沒有這麼做,仍保留了其原始的格式。有關保留項目的完整列表,請訪問 Python 核心開發站。如果您具備足夠的探索精神願意深入研究所有的 PEP,那麼您一定能夠從中獲得更深入的資訊。
本系列的下一期文章將會涵蓋更進階的主題,比如元類文法、ABC、修飾符、integer literal 支援、基底類型和異常。