python3的編碼有哪些坑?
看了幾位大神的回答,都是從py2,3的內置類型結合encode和decode操作說明的,那麼換個角度來問這個問題,如圖:
即如果從文本文件中讀進來的數據就是"xe4xb8xadxe6x96x87"這種形式的,在py3中應該如何正確顯示
以下是原問題
--------------------------------------------------------------------------------
py2會嘗試對str和bytes類進行隱式轉換,而在py3中則必須通過encode和decode操作才能對二者進行轉換,因此也會帶來一些直接print()不能正常顯示,需要進一步進行解碼才能正確顯示的問題,比如下圖分別是python2.7和python3.5中的結果:
各位在使用python3的過程中還遇到過哪些編碼問題,都是如何解決的呢?
這不是坑,encode的結果是個bytes對象,在Python2中bytes只是str的別名,這個實際上才是錯誤的。Python3里str是unicode,所有需要和人交互的地方都應該用str,你print一個bytes,自然是顯示bytes的__str__結果,因為沒有任何理由認為你bytes當中存儲的是個字元串,它完全可能只是普通二進位
類似的還有bytes[0]現在返回的是個整數而不是個字元。
py3 的字元串與 py2 的區別說穿了就是很簡單的對三種數據類型的處理。
py2 的方式意味著字元串跟位元組流是相同的東西。而unicode字元串是某種獨特的類型。
bytes==str
unicode!=str
py3 的方式意味著字元串跟unicode字元串是相同的東西,而位元組流是某種獨特的類型。
unicode==str
bytes!=str
unicode是什麼呢?是某種特定編碼的位元組流,是bytes的子集。這就意味著:所有的unicode都能放進bytes,但某些bytes無法放進unicode。
C 程序員最難接受的就是無法將一串位元組流放進字元串。然而 py3 的字元串就是這種東西,根本沒有辦法將一串任意的位元組流放進去。實際上 Java 的字元串也無法放進任意的位元組流。
對 Java 程序員來說這可能天經地義,但對 C 程序員來說,字元串不能放進位元組流簡直太糟糕了!編碼前跟編碼後的數據竟然不能放在同一個數據類型中。
這也許就是最大的坑吧!py3 字元串無法放進位元組流,可能導致 C 基礎的程序員的不適應。反過來說,py2 能將位元組流放進字元串,卻會導致 Java 基礎的程序員的不適應。從文本文件中讀進來的數據就是"xe4xb8xadxe6x96x87"這種形式的
這件事就不應該發生
Python 3 中文編碼已經沒有"坑"了(所謂的"坑",是初學者編碼的知識沒有掌握)。Python 3 統一編碼為unicode ,字元串不需要在前面加u
字元串調用encode() 方法,變為bytes 類型,bytes類型 調用decode()方法,變為str類型。
根本不知道你在說什麼,你對 2 和 3 的編碼區別有了解嗎?
Python 2 str = Python 3 bytes
Python 2 unicode = Python 3 str
所以你的截圖裡 2 和 3 沒有區別,你只用 asciii 的字元,Python 2 的 str 和 Python 3 的 bytes 都能正常顯示的,你用非 ascii 字元再 encode,python 不可能猜得到你的 bytes 是什麼編碼,理所當然是給你 x 的顯示方式。
為什麼 Python 2 的很多方法會幫你在 str 和 unicode 中隱式轉換?因為標準庫的開發者也不知道究竟應該接受 str 還是接受 unicode,語義上都是字元串,只好幫你隱式轉換一下,但 Python 2 的 str 其實編碼是不確定的,你同一段 u"中文" 換成 GBK encode,得到的 str 結果就不一樣了。一個 str 類型連本來的意思都還原不出不是很逗嗎。。
所以 Python 3 統一 str 為 unicode 實在太合理了,唯一不合理的是沒早這麼干。Python 2 的 str 就老老實實當 bytes,這才是他的本意。同意python3編碼沒有坑,但是你要了解各種轉換方法和技巧。可能需要import不同的庫來轉。
py3沒有unicode,py2中沒有bytes,所以不存在什麼py2中的str到bytes的隱式轉換,因為py2的str就是bytes,只不過print了你看著像str,py3中也沒你描述的那種的編碼問題,py3中的u開頭的str就是str,只是保留了u,看起來像unicode。所以你還是沒理解py的編碼,理解了就不會問encode的問題了。
可能語言描述比較混亂,舉個例子就清楚了,比如在requests庫的compat.py文件中,你可以看到這樣的版本兼容代碼(節選)。
_ver = sys.version_info
is_py2 = (_ver[0] == 2)
is_py3 = (_ver[0] == 3)
if is_py2:
bytes = str
str = unicode
elif is_py3:
str = str
bytes = bytes
所以回到問題,python3的編碼的坑?不存在的。
推薦這兩篇文章:基本上字元編碼的坑都能解決了
Python2 編碼為什麼那麼蛋疼?Python3 是如何解決棘手的字元編碼問題的?
有的答案看起來難,是因為問題本身難。計算機工程裡面有大量這種看起來簡單的問題。
一個.py文件裡面有一行代碼str="字元串string",代碼文件本身有utf8,unicode,gbk等可選格式,你會讓str變數以什麼形式存儲在內存里?
如果str是從某個文件讀的,文件本身有多種格式,你想讓它怎麼存儲在內存里?
你要把str輸出到文件,目標文件的編碼方式肯定要指定,是否需要指定變數的編碼方式?
如何正確與stdin/stdout交互以保證不會得到亂碼?
不要以為寫if很low。邏輯分支只能組合的比較優雅,但是無法省略。多下下象棋,多看看人文書籍,別老沉浸在技術裡面,才是學編程的正道。
我從另外一個角度來說說吧,為了填上這個坑,我們來看看這個坑的深處埋了些什麼。
我用我專欄里的一篇文章(專欄鏈接:給妹子講python)來仔細講講編解碼的來龍去脈,搞清楚這些我覺得能從根本上解決問題:
關於字元編碼的概念太多太雜,當例如ASCII、GB2312、Unicode、UTF-8、UTF-16、編碼、解碼等諸多名詞一股腦的堆上來時,確實容易讓人迷糊,為了把這些個問題都能講清楚,我們今天換一種講法,不講編程,只講故事,從時間軸上梳理計算機在不同語言國家不斷發展的歷史,來徹底搞清楚這些概念。
計算機自己能理解的「語言」是二進位數,最小的信息標識是二進位位,8個二進位位表示一個位元組;而我們人類所能理解的語言文字則是一套由英文字母、漢語漢字、標點符號字元、阿拉伯數字等等很多的字元構成的字符集。如果要讓計算機來按照人類的意願進行工作,則必須把人類所使用的這些字符集轉換為計算機所能理解的二級制碼,這個過程就是編碼,他的逆過程稱為解碼。
最開始計算機在美國發明使用,需要編碼的字符集並不是很大,無外乎英文字母、數字和一些簡單的標點符號,因此採用了一種單位元組編碼系統。在這套編碼規則中,人們將所需字符集中的字元一一映射到128個二進位數上,這128個二進位數是最高位為0,利用剩餘低7位組成00000000~01111111(0X00~0X7F)。0X00到0X1F共32個二進位數來對控制字元或通信專用字元(如LF換行、DEL刪除、BS退格)編碼,0X20到0X7F共96個二進位數來對阿拉伯數字、英文字母大小寫和下劃線、括弧等符號進行編碼。將這套字符集映射到0X00~0X7F二進位碼的過程就稱為基礎ASCII編碼,通過這個編碼過程,計算機就將人類的語言轉化為自己的語言存儲了起來,反之從磁碟中讀取二級制數並轉化為字母數字等字元以供顯示的過程就是解碼了。
隨著計算機被迅速推廣使用,歐洲的非英語國家的人們發現這套由美國人設計的字符集不夠用了,比如一些帶重音的字元、希臘字母等都不在這個字符集中,於是擴充了ASCII編碼規則,將原本為0的最高位改為1,因此擴展出了10000000~11111111(0X80~0XFF)這128個二進位數。這其中,最優秀的擴展方案是ISO 8859-1,通常稱之為Latin-1。Latin-1利用128~255這128個二進位數,包括了足夠的附加字符集來涵蓋基本的西歐語言,同時在0~127的範圍內兼容ASCII編碼規則。
隨著使用計算機的國家越來越多,自然而然需要編碼的字符集就越來越龐大,早先的ASCII編碼字符集由於受到單位元組的限制,其容量就遠遠不夠了,比方說面對成千上萬的漢字,其壓力可想而知。因此中國國家標準總局發布了一套《信息交換用漢字編碼字符集》的國家標準,其標準號就是GB 2312—1980。這個字符集共收入漢字6763個和非漢字圖形字元682個,採用兩個位元組對字符集進行編碼,並向下兼容ASCII編碼方式。簡言之,整個字符集分成94個區,每區有94個位,分別用一個位元組對應表示相應的區和位。每個區位對應一個字元,因此可用所在的區和位來對漢字進行兩位元組編碼。再後來生僻字、繁體字及日韓漢字也被納入字符集,就又有了後來的GBK字符集及相應的編碼規範,GBK編碼規範也是向下兼容GBK2312的。
在中國發展的同時,計算機在全世界各個國家不斷普及,不同的國家地區都會開發出自己的一套編碼系統,因此編碼系統五花八門,這時候問題就開始凸顯了,特別是在互聯網通信的大環境下,裝有不同編碼系統的計算機之間通信就會彼此不知道對方在「說」些什麼,按照A編碼系統的編碼方式將所需字元轉換成二進位碼後,在B編碼系統的計算機上解碼是無法得到原始字元的,相反會出現一些出人意料的古怪字元,這就是所謂的亂碼。
那麼統一字元編碼的需求就迫切擺在了大家眼前,為了實現跨語言、跨平台的文本轉換和處理需求,ISO國際標準化組織提出了Unicode的新標準,這套標準中包含了Unicode字符集和一套編碼規範。Unicode字符集涵蓋了世界上所有的文字和符號字元,Unicode編碼方案為字符集中的每一個字元指定了統一且唯一的二進位編碼,這就能徹底解決之前不同編碼系統的衝突和亂碼問題。這套編碼方案簡單來說是這樣的:編碼規範中含有17個組(稱為平面),每一個組含有65536個碼位(例如組0就是0X0000~0XFFFF),每一個碼位就唯一對應一個字元,大部分的字元都位於字符集平面0的碼位中,少量位於其他平面。
既然提到了Unicode編碼,那麼常常與之相伴的UTF-8,UTF-16編碼方案又是什麼?其實到目前為止我們都一致混淆了兩個概念,即字元代碼和字元編碼,字元代碼是特定字元在某個字符集中的序號,而字元編碼是在傳輸、存儲過程當中用於表示字元的以位元組為單位的二進位序列。ASCII編碼系統中,字元代碼和字元編碼是一致的,比如字元A,在ASCII字符集中的序號,也就是所謂的字元代碼是65,存儲在磁碟中的二進位比特序列是01000001(0X41,十進位也是65),另外的,如在GB2312編碼系統中字元代碼和字元編碼的值也是一致的,所以無形之中我們就忽略了二者的差異性。而在Unicode標準中,我們目前使用的是UCS-4,即字符集中每一個字元的字元代碼都是用4個位元組來表示,其中字元代碼0~127兼容ASCII字符集,一般的通用漢字的字元代碼也都集中在65535之前,使用大於65535的字元代碼,即需要超過兩個位元組來表示的字元代碼是比較少的。因此,如果仍然依舊採用字元代碼和字元編碼相一致的編碼方式,那麼英語字母、數字原本僅需一個位元組編碼,目前就需要4個位元組進行編碼,漢字原本僅需兩個位元組進行編碼,目前也需要4個位元組進行編碼,這對於存儲或傳輸資源而言是很不划算的。因此就需要在字元代碼和字元編碼間進行再編碼,這樣就引出了UTF-8、UTF-16等編碼方式。基於上述需求,UTF-8就是針對位於不同範圍的字元代碼轉化成不同長度的字元編碼,同時這種編碼方式是以位元組為單位,並且完全兼容ASCII編碼,即0X00-0X7F的字元代碼和字元編碼完全一致,也是用一個位元組來編碼ASCII字符集,而常用漢字在Unicode中的字元代碼是4E00-9FA5,在文末的對應關係中我們看到是用三個位元組來進行漢字字元的編碼。UTF-16同理,就是以16位二進位數為基本單位對Unicode字符集中的字元代碼進行再編碼,原理和UTF-8一致。
因此,我們可以看出,在目前全球互聯的大背景下,Unicode字符集和編碼方式解決了跨語言、跨平台的交流問題,同時UTF-8等編碼方式又有效的節約了存儲空間和傳輸帶寬,因而受到了極大的推廣應用。
可以再參考一下其他答主的具體代碼進行具體問題分析,我就講講宏觀的吧~
python3的編碼沒有坑。
唯有你的不理解造成的坑罷了。
str是一種高層對象。bytes是一種底層的東西。這麼理解就很容易明白了
如果你需要在高層處理,那麼就全部使用str。
在與外部通信的時候,比如保存文件,socket通信,使用bytes。
這裡一點坑都沒有。很多人所以會產生疑問,是因為python2的時代概念混淆的原因
作為弱類型動態語言,string當byte[]沒什麼大意義,百分之九十九的使用環境下都是在處理文本,string就應該是char[],byte stream就應該是byte[],這才符合輕型,膠水,腳本,Web的定位設定。
python3改進還是不錯的,就是造成了持續快十年的分裂了,蛋疼。
list賦值,以及各種賦值,經常出錯。如果a=[1,2,3],然後b=a,那麼如果a改變了,比如增加了一個元素,b也跟著變。所以這種賦值要用b=copy(a)才行
utf-8的數據流是保存為bytes類型,不帶編碼信息,而轉成unicode就不是原來的數據了。在輸入輸出的時候是使用的bytes,然後要手工轉成unicode。我覺得bytes類型應該自帶編碼信息,往文件類型讀寫unicode時可以自動轉化,或者得到帶編碼信息的bytes。
py3已經是很完善,只在讀取網路位元組流時候需要多嘗試,各種encode decode!.
本地讀寫字元串,沒有任何問題。
看樓主的問題,還是有些沒有描述清楚的地方:Python讀取文本文件後得到的str是"xe4xb8xadxe6x96x87",還是"\xe4\xb8\xad\xe6\x96\x87"?
- 如果讀取得到的str是"xe4xb8xadxe6x96x87",則有可能是樓主讀取文件時的encoding設置錯誤(例如ISO-8859-1)。這種情況下可以進行str_val.encode("iso-8859-1").encode("utf-8")得到正確字元串;或者更改讀取文件時用的encoding。
- 如果讀取得到的str是"\xe4\xb8\xad\xe6\x96\x87",有一種粗暴解決方法是用正則表達式替換,例如(sorry,不知道怎麼貼代碼...):
不知道我理解有沒有錯誤,如果有問題,請多多指出。
語言本身應該不會有什麼坑吧。
主要是第三方庫,我現在做的一個項目,本來是用py3,可是後來安裝一些第三方庫,死活裝不成功,一怒之下換回py2。好像是mysql-python庫,有點記不清了
這不叫坑。
這就好像你在py3下寫
print 1
然後報錯, 然後來知乎提問
python3 print語句有哪些坑一樣。
是沒有知道他的用法, 而不算他有坑。
至於用法, 其他人已經說了。
py2到py3的字元處理的進步是我使用py3x的最大動力,以至於剛開始的時候py3x很多lib都沒有依舊阻擋不了我
py2x的時候是很煩的,爬蟲,資料庫,但凡可能涉及到編碼的地方都要去處理編碼,輸入輸出用的是什麼編碼,我py文件本身聲明的又是什麼編碼,一會頭緒就亂了
py3x的時候除了在處理socket通信的時候用到了一次編碼解碼沒第一時間反應過來,其他時候都非常省心
py3x坑,不存在的所有文件讀寫相關操作,都要指定編碼,不要擅自圖省事,不要擅自圖省事……
請仔細閱讀並運行以下代碼,解決你90%問題
import json
import locale
print(locale.getpreferredencoding())
dict1 = {"中華": 123, "明代": 456}
# print(json.dumps(dict1, ensure_ascii=False))
with open("p1.json", "w", encoding="utf-8") as f:
json.dump(dict1, f, ensure_ascii=False)
with open("p1.json", "r", encoding="utf-8") as f:
data1 = json.load(f)
print("The data is ", data1)
with open("p1.json", "rb") as f:
data2 = json.load(f)
print("The data is ", data2)
with open("p1.json", "rb") as f:
d1 = f.read()
print(type(d1), d1)
print(d1.decode("utf-8")) # 已經轉碼,但未賦值
# other way
data3 = json.loads(d1)
print("The data is ", data3)
# other thing:
d2 = json.dumps("宋代", ensure_ascii=False)
print(d2)
byte_d2_gbk = d2.encode("GBK")
byte_d2_u8 = d2.encode("utf-8")
print(byte_d2_gbk)
print(byte_d2_u8)
d3 = json.loads(byte_d2_u8)
print(d3)
# d3_error = json.loads(byte_d2_gbk)
另:
1、二進位格式(bytes格式)讀寫,不指定編碼;
2、https://docs.python.org/3/library/json.html#json.dump
json.loads(s, *, encoding=None, cls=None, object_hook=None, parse_float=None, parse_int=None, parse_constant=None, object_pairs_hook=None, **kw)
Deserialize s (a str, bytes or bytearray instance containing a JSON document) to a Python object using this conversion table.
The other arguments have the same meaning as in load(), except encoding which is ignored and deprecated.
If the data being deserialized is not a valid JSON document, a JSONDecodeError will be raised.
Changed in version 3.6: s can now be of type bytes or bytearray. The input encoding should be UTF-8, UTF-16 or UTF-32.
json.loads可以自行處理UTF8的轉碼bytes
推薦閱讀:
※localhost、127.0.0.1 和 本機IP 三者的區別?
※為何vxlan需要封裝在UDP里而不是直接使用IP包封裝?
※操作系統接駁網路連接設備後,都是怎樣判斷已經成功連入Interent?
※tcp詳解v1 真的適合初學tcp/udp編程嗎?
※TCP/IP 協議到底在講什麼?