iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 14
1
Data Technology

30天python雜談系列 第 14

版本差異雜談之四———open之亂談無極限

  • 分享至 

  • xImage
  •  

python 版本差異雜談之四

囉唆一下:跟你們說,今天實在是鑽牛角尖到不行,而且後來發現好像沒必要,但既然寫都寫了,而且對於某些讀者應該也有幫助,就拿來獻醜一下XD

差異四:open函數的差異

最近的內容總是跟UnicodeError雜談重複很多,但為了好好整理並解釋兩個版本之間的功能差異,我還是多少重述一遍,但會試著提供更多知識或是用令一種說法來說明。

從python2轉換到python3的過程中,還會有一個很明顯的感受就是open函數改變了,假設現在有一個example.txt,裏面有一行字串'我是範例',試著比較一下python2和python3兩個版本的open函數的行為:

In python3 shell:
>>> example_content = open('example.txt','rt')
>>> example_content.read()
'# -*- coding: utf-8 -*-\n我是範例\n'
>>> type(example_content.read())
<class 'str'>
>>> 

In python2 shell:
>>> example_content = open('example.txt','rt')
>>> example_content.read()
'# -*- coding: utf-8 -*-\n\xe6\x88\x91\xe6\x98\xaf\xe7\xaf\x84\xe4\xbe\x8b\n'
>>> type(example_content.read())
<type 'str'>
>>> 

雖然兩個的type看起來是一樣的,但記得前天所說,兩個版本的'str'是不一樣的,在python2所說的str物件,等於未經解碼的bytes字串,所以說,python2不會去對檔案的字串內容做處理,但是python3會試著去對內容做解碼,相信大家在這麼多天的講解之後已經初步了解為何會作這種改變,為的是要在程式裡呈現出字符真正的樣子,而不是無法理解的編碼資料。

我們先用help函數來看看python3的open功能和python2有什麼差異:

Python2:

open(...)
    open(name[, mode[, buffering]]) -> file object

Python3:

open(...)
    open(file, mode='r', buffering=-1, encoding=None,
         errors=None, newline=None, closefd=True, opener=None) -> file object

先略過help(open)以下的說明不看,python3的open函數多出了許多設定參數,其中encoding決定了要用何種編碼來解碼檔案內容,errors我使用過的模式有'replace'和'ignore',在UnicodeError雜談有說明過,不在此贅述,當然errors還有其他的模式,以下式help(open)文件的一段說明:

See the documentation for codecs.register or run 'help(codecs.Codec)' for a list of the permitted encoding error strings.

因此我們試著照著文件說明去做:

In python3 shell:
>>> import codecs
>>> help(codecs.Codec)

出現以下參數說明:

 |   'strict' - raise a ValueError error (or a subclass)
 |   'ignore' - ignore the character and continue with the next
 |   'replace' - replace with a suitable replacement character;
 |              Python will use the official U+FFFD REPLACEMENT
 |              CHARACTER for the builtin Unicode codecs on
 |              decoding and '?' on encoding.
 |   'surrogateescape' - replace with private code points U+DCnn.
 |   'xmlcharrefreplace' - Replace with the appropriate XML
 |                         character reference (only for encoding).
 |   'backslashreplace'  - Replace with backslashed escape sequences
 |                         (only for encoding).

再細說的話就太煩瑣了,有興趣了人就自己嘗試看看吧!至於python2因為不會自動對檔案內容解碼,所以自然不會有encoding和errors設定。

那newline參數是什麼意思呢?他和檔案的換行議題有些相關,很多人應該都知道,unix系統和windows系統的換行符號是不一樣的,一個是'\n'而一個是'\r\n',當要執行for line in file語法時,python就必須意識到這個問題,而預設的處理方法是所謂的"universal newline",在讀取檔案時,不管是遇到'\n','\r','\r\n'都會被認為是換行符號並再讀取時轉譯為'\n',至於其他模式的功能如何,可以在下面做個測試:

In python3 shell:
>>> example_file=open('example_newline','wt')
>>> example_file.write('我是第1行\n我是第2行\r我是第3行\r\n我是第4行') # 先建立一個內含三種換行符號的檔案,然後離開python
19

In python3 shell:
>>> example_file=open('example_newline','rt')
>>> for line in example_file: # 能夠有效辨認每種換行符號
...     print(line)
... 
我是第1行

我是第2行

我是第3行

我是第4行

>>> example_file=open('example_newline','rt')
>>> example_file.read() # 讀取時會自動換做'\n'
'我是第1行\n我是第2行\n我是第3行\n我是第4行'

>>> example_file=open('example_newline','rt',newline='')
>>> for line in example_file: # 能夠有效辨認每種換行符號
...    print(line)
... 
我是第1行

我是第2行
我是第3行

我是第4行
>>> example_file=open('example_newline','rt',newline='')
>>> example_file.read() # 讀取時換行字元不會被轉譯
'我是第1行\n我是第2行\r我是第3行\r\n我是第4行'
>>> 

>>> example_file=open('example_newline','rt',newline='\n')
>>> for line in example_file: # 第2行消失了!!
...    print(line)
... 
我是第1行

我是第3行

我是第4行
>>> example_file.read() # 讀取時換行字元不會被轉譯
'我是第1行\n我是第2行\r我是第3行\r\n我是第4行'

先把for line in example_file產生的一些疑問放下,先記一下結論,除了預設的"universal newline"會轉譯所有換行字符為'\n',而其他模式都不會,然後newline=''的模式也會辨識所有的換行符號,而其餘的'\n','\r','\r\n'模式都只會辨認其指定的換行符號。

那...除了預設模式,其他模式的for line in example_file打印出來的結果到底是怎麼回事,尤其是newline='\n'那個,第2行消失了真是看的霧煞煞,其實我在測試的當下也是非常納悶的,但後來有查到不錯的資料讓我解開疑惑:http://www.pythontab.com/html/2017/linuxkaiyuan_0115/1116.html

這位解答了我疑問的大大說'\r'是指回車,而'\n'是換行,在以前的打字機中,回車就是把打字游標歸位到行首,而換行,就真的是換行,我們現在把ubuntu的terminal當成打字機來理解,在版本差異系列的第一篇就有說過print函數有一個名為end的設定結尾符號的參數,預設是'\n',所以在newline模式為"universal newline"的狀況下,首先每個換行符號都被轉譯成'\n',而for迴圈中程式既讀到了每一行的結尾的'\n'(我是第1行「\n」),加上print結尾符號的'\n',一共是兩個'\n',所以就出現了每一行之間又空出一格的輸出:

我是第1行

我是第2行

我是第3行

我是第4行

在newline=''的模式下,雖會辨認每個換行符號,但不會都轉譯為'\n',所以會print的每一行各是'我是第1行\n','我是第2行\r','我是第3行\r\n',因為第2行的內容並沒有換行符號,所以只有print結尾符號的'\n',也就是只會換一次行,但第1行和第3行會換兩次行,所以導致以下結果:

我是第1行

我是第2行 <---只換一次行
我是第3行

我是第4行

那回車符號有造成什麼影響嗎,在newline=''的輸出其實看不太出來,但在newline='\n'就很明顯了,因為只辨認'\n'為換行字符,所以基本上只會打印三行:'我是第1行\n' 還有 '我是第2行\r我是第3行\r\n' 還有 '我是第4行',第1行沒什麼問題,但在'我是第2行'之後的'\r'會讓游標移至行首,然後就會被'我是第3行'覆蓋掉,接著的'\r\n'就只是再移到行首並換行,因此不會對輸出產生影響:

我是第1行

我是第3行 <---把'我是第2行'覆蓋掉

我是第4行

若想再更深刻體會'\r'的效果可以執行以下指令:

In python3 shell:
>>> print('我是最原始的而且比較長的字串lalala\r我想要覆蓋原始字串haha\r再覆蓋一次')
再覆蓋一次原始字串haha的字串lalala

好了,今天讓我休息一下八,我不知道open函數這麼折騰人,打著打著就是一天的份,而且還沒打完,可能是太追根究底了,但秉持著事實求是的精神,我會繼續努力下去的。


上一篇
版本差異雜談之三———字串輸出與除法
下一篇
版本差異雜談之五———懶得談open了&加個range充字數
系列文
30天python雜談30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言