Python 3 是 Guido van
Rossum 功能強大的通用編程語言的最新版本。它雖然打破了與 2.x 版本的向後兼容性,但卻清理了某些語法方面的問題。本文是系列文章中的第一篇,介紹了影響該語言及向後兼容性的各種變化,並且還提供了新特性的幾個例子。
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)的編程人員。(本文後面的 參考資料 部分提供了有關這些
PEP 的鏈接。)
新的 print() 函數
如今,您將需要讓手指習慣於鍵入 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 字符串:
>>>repr('é') "'é'"
正如我之前提到的,這個字符串是內置的字符串類型。
字符串對象和字節對象是不兼容的。如果想要得到字節的字符串表示,需要使用它的 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 membership True
如下是在 dict_values
對象的條目上進行迭代的例子:
>>>for values in d.items(): ... print(values) ... dead parrot
不過,如果您的確想要得到值的列表,可以對所返回的 dict
對象進行強制類型轉換。比如:
>>>keys = list(d.keys()) >>>print(keys) [1,2]
新的 I/O
在深入研究 I/O 的新機制之前,很有必要先來看看抽象基類( abstract base classes,ABC)。更深入的介紹將會在本系列的第 2 部分提供。
ABC 是一些無法被實例化的類。要使用 ABC,子類必須繼承自此 ABC 並且還要覆蓋其抽象方法。如果方法的前綴使用 @abstractmethod
修飾符(decorator),那麼此方法就是一個抽象方法。新的
ABC 框架還提供了 @abstractproperty
修飾符以便定義抽象屬性。可以通過導入標準庫模塊 abc
來訪問這個新框架。清單
1 所示的是一個簡單的例子。
清單 1. 一個簡單的抽象基類
from abc import ABCMeta class SimpleAbstractClass(metaclass=ABCMeta): pass SimpleAbstractClass.register(list) assert isinstance([], SimpleAbstractClass)
register()
方法調用接受一個類作爲其參數並會讓此
ABC 成爲所註冊類的子類。這一點可以通過在最後一行上調用 assert
語句進行驗證。清單
2 是使用修飾符的另外一個例子。
清單 2. 使用修飾符的一個抽象基類
from abc import ABCMeta, abstractmethod class 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()
。