(4) 和字串物件相關的str與repr方法
在str與repr雜談之中我們有說過,如果在python shell調用了print function,其輸出是__str__返回的字串,若是直接輸入變數,輸出則為__repr__返回的字串,而python2和python3對str和repr表現出的行為是不太一樣的,所以就連帶著輸出也會不太一樣,我們現在來討論一下關於這兩種類型的字串物件在兩個版本裡的兩個輸出行為究竟如何(有2x2x2=8種組合,好多好討厭...):
In python3 shell:
>>> print(str('範例')) # 相當於 >>> print('範例')
範例
>>> print(repr('範例')) # 相當於 >>> '範例'
'範例'
>>> print(str('範例'.encode('utf-8'))) # 相當於 >>> print('範例'.encode('utf-8'))
b'\xe7\xaf\x84\xe4\xbe\x8b'
>>> print(repr('範例'.encode('utf-8'))) # 相當於 >>> '範例'.encode('utf-8')
b'\xe7\xaf\x84\xe4\xbe\x8b'
In python2 shell:
>>> print(str('範例')) # 相當於 >>> print('範例')
範例
>>> print(repr('範例')) # 相當於 >>> '範例'
'\xe7\xaf\x84\xe4\xbe\x8b'
>>> print(str(u'範例')) # oops!這個竟然會出現UnicodeEncodeError[註一]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)
>>> print(u'範例') # 先不要管上面的UnicodeEncodeError
範例
>>> print(repr(u'範例')) # 相當於 >>> u'範例'
u'\u7bc4\u4f8b'
雖然範例出現了UnicodeEncodeError[註一],但這不在我們今天的討論範圍,現在我們的重點是要探討輸出行為,雖然有一個例外狀況出現,但還是先記著,調用了print function,其輸出是__str__返回的字串,若是直接輸入變數,輸出則為__repr__返回的字串。
可以看出python3對於輸出的行為與類別的名稱是互相呼應的,不管是__str__還是__repr__,str物件會以文字的方式顯示,Bytes物件則是以位元組的方式呈現,其前綴b只是代表現在是Bytes物件。
python2的輸出行為是以__str__和__repr__做為區隔,只要是調用了print function(調用__str__),就會以可讀字串顯示,而直接輸入值(調用__repr__)的話,就會以位元組的方式顯示。
既然python2的__repr__方法回傳的都會是位元組字串,不知道有沒有人想過一個問題,如果現在我執行了一個python程式,我想觀察裏面的容器內的字串,比如說某一個list裏面的字串內容,但在前幾天的repr與str雜談中,只要把list印出來,裏面的字串內容都會調用__repr__方法回傳的位元組字串顯示,所以我們無法從螢幕簡單的看出字串內容?!
test.py:
# -*- coding: utf-8 -*-
example = ['我想看到這裏面的內容']
print example
In bash:
$ python test.py
['\xe6\x88\x91\xe6\x83\xb3\xe7\x9c\x8b\xe5\x88\xb0\xe9\x80\x99\xe8\xa3\x8f\xe9\x9d\xa2\xe7\x9a\x84\xe5\x85\xa7\xe5\xae\xb9']
但要解決這個問題很簡單,有以下幾種方法:
如果要再繼續深究這些原理似乎就太偏題了,所以今天就就此打住吧,如果有好奇的朋友就自己查資料八XD
(5) 讀取py檔的預設編碼
在UnicodeError雜談也有談到這個問題,python2和python3對於py檔的讀取編碼是不一樣的:
In python2 shell:
>>> import sys
>>> sys.getdefaultencoding()
'ascii'
In python3 shell:
>>> import sys
>>> sys.getdefaultencoding()
'utf-8'
python2會用這麼原始又蛋疼的編碼主要是因為歷史原因,在1989年荷蘭程式設計師Guido van Rossum開發了python這套腳本語言,但在那個時候unicode(1991年由The Unicode Consortium發佈初版)還沒有出現,當然連utf-8也還沒有出來,在早期編碼系統各地發展的時代下,Guido van Rossum當然會使用時下最通用的ascii作為預設編碼。
也因為這樣,python2在讀取內含非ascii字元的python檔的時候,必須要在標頭加註# -- coding: utf-8 --這個魔法註解,指定python2必須以utf-8編碼來讀取py檔(當然也可以用這個註解來指定其他類型編碼),否則會出現SyntaxError,而python3因為預設使用utf-8編碼,所以不需要擔心這一個問題。
講完了有關字串處理在兩個版本間的差異,大家可能頭腦都累了(其實是我打到累了),接下來我就多塞個比較簡單的小知識好了。
先來個範例:
In python2 shell:
>>> 5/2
2
>>> 5//2
2
>>> 5/2.0
2.5
>>> 5//2.0
2.0
>>> type(5/2) # 整數相除的結果也是整數型態
<class 'int'>
In python3 shell:
>>> 5/2
2.5
>>> 5//2
2
>>> 5/2.0
2.5
>>> 5//2.0
2.0
>>> type(5/2) # 整數相除的結果變成float型態
<class 'float'>
>>> 4/2 # 就算是整除運算,結果也會是float
2.0
很明顯可以看出來python2對於'/'運算的結果是依賴於變數型態的,若是兩個整數相除,結果也會是整數型態,所以會把小數捨去,而python3的'/'運算改為精確除法,不管除數與被除數的變數型態,也不管是不是能整除,最後的結果都是float型態,因為要表現出精確結果,勢必要有小數。
而對於'//'運算和當初的python2並沒有分別,都是強制性的'截斷除法',對於小數會自動捨去,所以在python3這兩個運算符的作用就分別的更為明確了,'/'是精確除法,'//'是截斷除法,與除數和被除數的變數型態並沒有任何關係。
好了,終於把最煩雜的字串處理講完了,今天也講的夠多了,那大家明天再見囉!
[註一]
詳細原因可以參考網址:https://stackoverflow.com/questions/2596714/why-does-python-print-unicode-characters-when-the-default-encoding-is-ascii/21968640#21968640