2008年12月4日

Python 3.0 的新玩意(第一部)

真可說是「萬眾囑目」的 Python 3.0 終於在 2008/12/3 釋出了!有興趣了解 Python 3.0 和 Python 2.x 有什麼不同的人可以去讀 GvR 寫的 "What’s New In Python 3.0",這篇是我讀文的筆記。

先記一下,舊的 Python script 可以用 2to3 來轉成 Python 3.0 的 script,可是不見得 100% 轉得完整。

print 從敍述(statement)降級為函式(function)

我的愛好度:heartheart

print "x =", x 這樣的敍述吻別吧!從此要寫 print("x =", x) 了,不過多了一些 keyword arguments 可以修改 print() 的行為,如 sep= 指定字串之間的分隔字,end= 指定印完要加的字(取代預設的換行字元)和 file= 指定要輸出的檔案物件。

我知道 print 沒必要是個敍述,用函式就可以做得很好,又容易自己換掉(__builtins__.print = 新函式就好了),但是為了印個東西打一堆括弧實在很煩,更何況大部分是為了 debug!這時候就想起 iPython 的好處了...

大量用 iterator 取代 list

我的愛好度:heartheartheartheartheart

早就該這麼做了!從此在大多數情形都能用 iterator,在處理大的 list 時就不用擔心會在記憶體多生一個大 list 跑不動了。

xrange()dict.iteritems() 都是極度礙眼的東東,從此完全消失!range()dict.items() 現在傳回的就是 iterator 了。

map()filter() 也傳回 iterator,直接用在迴圈裡又快又省。

比大小

我的愛好度:heartheartheartheart

我常用的 cmp() 和較少用的 __cmp__() 都消失啦!那要對複雜物件組成的清單(list)排序的時候怎麼辦?

看來要愛用 key= 和 operator 模組裡的 itemgetter()attrgetter() 兩個函式了。key= 指定一個函式,在排序時會把清單中的項目一一傳入,函式要傳回用來比較的資料。舉例來說,如果有個 list 是 aList = [ (5,'a'), (3,'z'), (7,'h'), (9,'o') ],要依項目一號排序就這樣寫:
  sorted(aList, key=operator.itemgetter(1))
結果會是
  [(3, 'a'), (1, 'h'), (9, 'o'), (5, 'z')]
如果有個 class Foo 是這樣:
class Foo:
def __init__(self, x, y): self.x, self.y = x, y
def __repr__(self): return "(%s,%s)" % (self.x,self.y)
要把 bList = [Foo(3,'a'), Foo(9,'o'), Foo(5,'o'), Foo(1,'h')] 依 y 屬性排序就這樣寫:
  sorted(bList, key=operator.attrgetter('y')) # 結果 [(3,a), (1,h), (9,o), (5,o)]
要先依 y 排序、再依 x 排序的話:
  sorted(bList, key=operator.attrgetter('y','x')) # 結果 [(3,a), (1,h), (5,o), (9,o)]

整數變大了

我的愛好度:heartheartheart

不用再管 long 和 int 的差別了,不用再見到數字後面那個沒必要的 L 了,因為所有的整數都是無限位數了!(喔,當然記憶體容量是個限制。)好棒啊!

不過 3/5 變成浮點除法 = 0.6,3//5 才是整數除法 = 0。完全記不得以前寫的程式那裡用到整數除法了啊!2to3 不但不會轉除法,連警告都沒有。 Orz

還有八進位整數的表示法也從 0127 變成 0o127(零+英文字母 o + 數字),還好 2to3 抓得到。

字串變簡單了

我的愛好度:heartheartheartheart

以前寫 Python 2.x 的 script 常常在處理中文的時候被文字編碼和解碼搞得昏頭,記不得哪個變數是 unicode、哪個變數是 str,尤其是當資料是從檔案讀進來的時候,然後一個簡單的 c = a + b 都可以因為 a 是 str、b 是 unicode 而吐個很難懂的
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)
錯誤訊息。喂!我明明在 script 檔頭指定了 # -*- coding: utf8 -*- 你還搞 ASCII codec?

在 Python 2.x 中直接用引號夾起來的是 str,內容是用 UTF8、Big5、Shift-JIS 等某個編碼系統編好的字串,想建立 unicode 物件的話,要用 str.decode() 來解碼,或是用 u"..." 來夾住文字。
  a = "中文"; b = u"字串"; c = "測試".decode("utf8")
print type(a), a, type(b), b, type(c), c # <type 'str'> 中文 <type 'unicode'> 測試 <type 'unicode'> 測試
這裡 c 是從 string literal decode 得到的 unicode 物件。

到 Python 3.0 的時代,所有 str type 的字串都內建以 unicode 儲存,不再另有 unicode 這個 type,而編碼過的字串或 binary 的資料則用新的 bytes type 儲存。從 str 到 bytes 是 encode,從 bytes 到 str 是 decode。
  a = "中文"; b = u"字串"; c = "測試".encode("utf8")  # b 會有錯誤訊息,因為已經沒必要用 u"..." 語法了
print(type(a), a, type(c), c) # <class 'str'> 中文 <class 'bytes'> b'\xe6\xb8\xac\xe8\xa9\xa6'
這裡 c 是從 str literal 編碼而得的 UTF8 內容,在 Python 3.0 裡是個 binary 的 bytes 物件。

換句話說,不再有字串和 unicode 字串兩種字串了,字串一律用 unicode 表示,非 unicode 的資料則是 bytes,可以用 b"..." 表示。

在 Python 3.0 裡把 str 和 bytes 加起來會得到比較好懂的錯誤訊息:
  TypeError: Can't convert 'bytes' object to str implicitly
開檔也不一樣了。open(filename) 是用 text mode 開啟,所以一定會用某種編碼去解釋讀到的資料,轉成 str 放在記憶體裡(記住,str 是 unicode),預設的編碼會從環境變數 LANG 決定,但可以用 open(filename, encoding="...") 指定編碼系統。如果不想把檔案裡的資料轉成 unicode,或者資料根本不是文字而是 binary data,就可以用 open(filename, "b") 來開檔。用 "b" (binary mode) 開檔讀 bytes data 的確比較容易理解。

非英文檔名也有支援,不過和作業系統有關。

辨識名可以用 UTF8 字元

我的愛好度:heartheartheartheart

變數名、函式名、class 名都可以用中文了!
  彤彤生日 = datetime.date(1997, 7, 5); print(彤彤生日)
這樣子周蟒的功能有一部分就直接被 Python 3.0 實現,更方便了。


(待續)

3 則留言 :

  1. 請問一下
    print 由敘述降級成函式
    會有什麼差別嗎

    小弟還是python的新手,不太懂得兩者間的差異

    回覆刪除
  2. 一言難盡... 總之是程式寫起來感覺不同、爽度不同。不過時間久了,習慣了應該也就好了。

    回覆刪除