python把一個unicode字元串寫入文件為什麼會報錯?

#coding=utf-8
s = u"知乎"
#s = "知乎"
fp = open("test.txt","w")
fp.write(s)
fp.close()

直接寫入unicode會報錯『ascii』 codec can"t encode character in position 0-3。而如果是s="知乎",就不會出現這個錯誤。開頭不是指定了utf-8嗎?難道打開文件的時候,編碼格式變成了ASCII?


看這個效果估計是py2,特此作答。

write方法的參數類型是str,str是二進位流(不包含編碼信息),當你給出一個unicode對象時,會執行str函數轉換成str類型再送給write方法。unicode轉str包含一次編碼,如不指定則默認使用ascii編碼,而ascii編碼集里漢字字元是沒有對應的,所以報錯。

然後代碼首行的編碼標記是用來告訴解釋器這個程序的源代碼是用什麼編碼存的。

正確姿勢是在代碼里指定編碼。比如在open里指定,或者在write的時候手動把unicode對象通過encode方法指定編碼產生str。就是說寫成fp.write(s.encode("utf8"))。注意unicode對象用encode是有意義的,str對象在py2里允許你對str對象使用encode,然而這是對指定了default encoding的情況下才有效的,因此不推薦新手對str直接encode。


python 2 +linux

這樣做


說 open 可以指定 encoding的,純粹誤人子弟

  1. 你的機器先會讀入這個腳本,按你系統的編碼格式把「知乎」翻譯成字元碼,

  2. 隨後Python的解釋器根據 "#encoding:utf-8" 來按utf-8的編碼規則來理解這些字元碼

  3. 當你把字元串寫到文件中去時,因為你的字元串是 unicode,所以Python會(自動)先調用encode方法來編碼unicode字元串,然後再寫入文件

  4. 當調用encode方法時,因為沒有指定編碼格式,所以採用默認值 ascii,ascii並不能解釋 1 中翻譯的字元碼,所以報錯

  5. 解決方法有兩種:
  • 把unicde格式的字元串轉換成utf-8,此時在寫入文件不會時,不會自動調用encode方法
  • 調用 codecs 模塊的 open 方法,並指定文件的編碼格式

# -*- coding:utf-8 -*-

import codecs

if __name__ == "__main__":
s = u"知乎"
s_utf8 = s.encode("utf-8")
with open("test.txt", "w") as f:
f.write(s_utf8)
with codecs.open("test2.txt", "w", encoding="utf-8") as f:
f.write(s)

注意,我假設你系統的格式兼容utf-8,否則上面的utf-8都應該換成你系統對應的編碼格式才能正常顯示文件內容。

我在 http://123.57.235.88:8123/2015/12/23/python_encoding_3.html 這兒整理過自己對Python編解碼的理解,你也許可以看看


開頭指定的是腳本編碼

打開文件的時候指定編碼可解:

fp= open("test.txt", "w", encoding="utf-8")


著作權歸作者所有。

商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

作者:Coldwings

鏈接:https://www.zhihu.com/question/37107423

來源:知乎

看這個效果估計是py2,特此作答。

write方法的參數類型是str,str是二進位流(不包含編碼信息),當你給出一個unicode對象時,會執行str函數轉換成str類型再送給write方法。unicode轉str包含一次編碼,如不指定則默認使用ascii編碼,而ascii編碼集里漢字字元是沒有對應的,所以報錯。

然後代碼首行的編碼標記是用來告訴解釋器這個程序的源代碼是用什麼編碼存的。

正確姿勢是在代碼里指定編碼。比如在open里指定,或者在write的時候手動把unicode對象通過encode方法指定編碼產生str。

就是說寫成fp.write(s.encode("utf8"))。注意unicode對象用encode是有意義的,str對象在py2里允許你對str對象使用encode,然而這是對指定了default encoding的情況下才有效的,因此不推薦新手對str直接encode。


回答這個問題,需要我們理清python中的字元種類及字元編碼方式,這樣就能徹底搞清楚類似的問題,我們一個一個的來介紹。

我用我專欄里的一篇文章來仔細講講:

專欄鏈接:給妹子講python,歡迎大家關注,提意見!

先來介紹一下Python中的兩種字元串

Python中有兩種字元串:文本字元串和位元組字元串。其中文本字元串類型被命名為str,內部採用Unicode字符集(兼容ASCII碼),而位元組字元串則直接用來表示原始的位元組序列(用print函數來列印位元組字元串時,若位元組在ascii碼範圍內,則顯示為ascii碼對應的字元,其餘的則直接顯示為16進位數),該類型被命名為bytes。

s = "apple"
b = b"apple"
print(b)
print(type(b))
print(s)
print(type(s))

b"apple"
&
apple
&

再近距離的看看bytes類型位元組字元串,本質上它就是一串單位元組16進位數

b = b"apple"
print(b[0])
print(b[1:])
print(list(b))

97
b"pple"
[97, 112, 112, 108, 101]

然後,再來說說編碼與解碼

從本質上來說,編碼和解碼就是str和bytes這兩種字元串類型之間的互相轉換。

str包含一個encode方法,用於使用特定編碼將其轉換為一個bytes,這稱之為編碼。bytes類包含了一個decode方法,也接受一個編碼作為單個必要參數,並返回一個str,這稱之為解碼。這種轉換操作是顯式的操作,且必須根據數據被編碼時採用的編碼類型進行解碼。

首先說說編碼,即將unicode的str文本字元串轉換為bytes的位元組字元串,可以顯示傳入指定編碼(一般採用utf-8編碼),或使用平台的默認編碼

s = "π排球の"
b1 = s.encode("utf-8")
b2 = s.encode()
print(b1)
print(b2)

b"xcfx80xe6x8ex92xe7x90x83xe3x81xae"
b"xcfx80xe6x8ex92xe7x90x83xe3x81xae"

那麼我們看看,在不寫編碼的時候,平台默認的編碼方式到底是什麼

import sys
print(sys.platform)
print(sys.getdefaultencoding())

win32
utf-8

可以看出平台默認選擇的是utf-8編碼方式。

接下來我們來比較一下unicode、latin-1、ASCII編碼方式的兼容性問題:

首先,非ASCII字元無法使用ASCII編碼轉換成位元組字元串

s = "π排球の"
b = s.encode("ascii")

Traceback (most recent call last):
File "E:/12homework/12homework.py", line 2, in &
b = s.encode("ascii")
UnicodeEncodeError: "ascii" codec can"t encode characters in position 0-3:
ordinal not in range(128)

其次,Latin-1和unicode編碼方式不兼容。

例如,重音字元會在latin-1字符集和unicode字符集中同時存在,但是通過latin-1和unicode編碼方式編出來的位元組流是不一樣的,注意,雖然unicode字符集是包含了latin-1字符集,但是不代表utf-8編碼方式兼容latin-1編碼方式。因為unicode字符集中除了ascii字符集外,都是採用多位元組的編碼方式,而latin-1一律採用單位元組的方式

s = "?è"
print(s.encode("utf-8"))
print(s.encode("latin-1"))

b"xc3x84xc3xa8"
b"xc4xe8"

只有ascii字符集中的字元,三種編碼方式得到的結果才完全一致。對unicode進行編碼的時候,針對常規的7位ASCII文本,由於utf-8以及latin-1編碼方式都是兼容ASCII的,所以結果都是一樣的。

s = "abc"
print(s.encode("utf-8"))
print(s.encode("latin-1"))
print(s.encode("ascii"))

b"abc"
b"abc"
b"abc"

再來談談decode解碼方法

將bytes類型字元串轉換成str類型的unicode文本字元串也是一樣,要麼指定編碼參數,要麼使用平台的默認參數。這個例子中,我們使用的位元組字元串b是通過utf-8編碼方式將"π排球の"編碼而形成的位元組字元串

b = b"xe6x8ex92xe7x90x83"
s1 = b.decode(encoding="utf-8")
s2 = b.decode()
s3 = b.decode(encoding="latin-1")

print(s1)
print(s2)
print(s3)

排球
排球
??』???

值得注意的是,最後一行代碼想通過latin-1解碼位元組字元串,由於位元組字元串是通過utf-8編碼形成,因此這樣解碼形成得到的就是亂碼。

Utf-8編碼是用兩個位元組來表示非ASCII的高128字元,而latin-1則是用一個位元組來一一對應

鋪墊了這麼多,我們再回到問題中來:python如何處理在文本文件讀寫過程中的字元編碼?

當一個文件以文本模式打開的時候,被讀取的二進位存儲數據(也就是存儲的位元組字元串)會自動被解碼(依據顯式提供的編碼名稱或平台默認的編碼名稱),並且將其返回為一個str。寫入文件時,會接受一個str,並且將其傳輸到文件之前自動編碼成位元組字元串。

當一個文件以二進位模式打開時,需要在open方法的模式字元串參數里添加一個b,此時讀取的數據不會以任何方式解碼,而是直接返回其原始內容,即一個bytes對象;寫入文件時,接受一個bytes對象,並且將其傳送到文件中且不進行修改。

在讀取文本文件的時候,如果open函數沒有聲明他們如何編碼,python3會因其所運行的系統而選取默認的編碼方式,默認情況下,python3 期望文件使用 utf-8進行編碼。但由於文件並不總是在同一個系統中被保存和打開,因此會帶來亂碼的風險,所以我們需要顯式的指定編碼。

補充的說明一下,可以很簡單的進行一個分類:處理圖像文件、設備數據流等,可以使用bytes和二進位模式文件處理;而如果要處理的內容實質是文本的內容,例如程序輸出、HTML、國際化文本或CSV或XML文件,則可能要使用str和文本模式文件

例如,我們先把A?BèC用UTF-8編碼後存入utf-8data文件,再來讀取他,具體看看這裡是如何實現的。

s = "A?BèC"

with open("utf-8data","w",encoding="utf-8") as f:
f.write(s)

with open("utf-8data","r",encoding="utf-8") as f:
u_str = f.read()
print(u_str)

A?BèC

這裡提到了文件讀寫的方法後面的章節會詳細介紹,現在知道他是什麼就好了。

以二進位的形式讀取文件。

還有一種方法,我們之前介紹過,文本字元串在存儲在磁碟的時候會編碼形成位元組字元,因此我們也可以先以位元組字元串的形式從文件中讀取位元組字元串,然後再進行解碼。這樣做的原因有二,一種是所接收的可能是非文本數據,如一個圖像文件,另一個潛在原因是無法確定所讀取文本文件的編碼,可能需要依據其他信息再確定

with open("utf-8data", "rb") as f:
byte_str = f.read()

print(byte_str)
print(byte_str.decode(encoding="utf-8"))

b"Axc3x84Bxc3xa8C"
A?BèC

這些內容應該有助於我們更好的在文本處理中使用python


推薦閱讀:

正則表達式如何匹配網頁裡面的漢字?
已經有了各省的數據,如何將信息以可視化的方式顯示在地圖上?順便問一下python有相關的第三方包沒?
可以用 Python 來幹些什麼有趣的事?
想要用 python 做爬蟲, 是使用 scrapy框架還是用 requests, bs4 等庫?

TAG:Python | Python入門 |