在 py2 中,有兩種類型字符串:str 和 unicode。但嚴格的來講,str 並非徹底意義上的字符串,把它稱做 字節碼串 更合適。unicode 則做爲真正意義上的 字符串,但定義時須要使用 u"" 去聲明。有人可能會有疑問,字符串 在內存單元中也是以字節進行存儲的,怎麼能將他倆區別對待呢?java
字符串 是一種 數據結構。數據結構 = 原數據 + 數據描述(爲了更容易理解),而字符串的 「數據描述」 即爲什麼種字符集。python
字節碼 則是沒有數據描述的。數組
py2 中,當咱們讀取 str 類型時,它只表明了一串數據,至於這串數據是何種編碼,系統是不知道的,須要咱們手動指定。而 unicode 類型,則存儲的相應的字符集,系統在讀取時便能根據此數據描述對數據進行識別和處理。數據結構
'\xc4\xe3\xba\xc3' 對計算機來講,就是 1 個字節的字節碼串在一塊兒。編碼
u'\u4f60\u597d' 對計算機來講,則是每 2 個字節做爲一個單位,去映射 unicode 字符集作運算。spa
當咱們定義 str字符串 時,python 會根據咱們當前的運行上下文:源文件 / cli 去設定 str 的字符集。好比咱們在腳本開始處註釋的 coding: utf-8 會將腳本中的 str 編碼爲 utf-8 的字節碼串兒。操作系統
# -*- coding: utf-8 -*- import sys """ 做用是註明腳本上下文的字符集 好比當咱們定義一個 str 類型的數據時 它是 utf-8 字符集的字節碼 """ str = "你好" #以 utf-8 字節碼去編碼 """ 但不要和 python 的默認字符集搞混 默認字符集會在一些隱式加解碼時被運用 """ # ascii print sys.getdefaultencoding() # 設置 python 的默認字符集 reload(sys) sys.setdefaultencoding("utf-8") """ 下面的代碼存在一次隱式的 decode 在隱式或沒有指定 decode 的字符集時 會使用 默認字符集(這裏已將默認字符集設爲 utf-8)進行解碼 若是咱們沒有修改默認字符集而使用 ascii 的話 就會報錯了 ascii 是沒辦法編碼中文的 """ "你好".encode("gb2312") """ py2中 write 寫的爲字節碼串 當前源碼編碼字符集爲 utf-8 因此寫入時 "你好" 是以 utf-8 編碼的 """ with open("encode.txt", "w") as f: f.write("你好") """ 讀取的 str 也爲 utf-8 的字節碼串 """ with open("encode.txt", "r") as f: str = f.read()
例如個人 cmd 是 gb2312 字符集,因此 "你好" 的字節碼串以下(gb2312 一箇中文字符用 2 個字節標識)。下面咱們將其轉換爲 utf-8 字符集的字節碼:code
encode/decode 下面詳細講解。內存
py3 則對字符串和字節碼進行了更爲規範的定義:str 和 bytes。str 終於成爲了真正意義上的字符串,bytes 也形象的表徵了字節碼串兒(java 中的字節數組)utf-8
py3 默認支持 unicode 字符集,故不須要雞肋的使用 py2 中的 u"xxx" 去定義一個 unicode 字符串。
簡單來講,py2 想定義一個平常的字符串,須要手動 u"xxx","xxx" 定義的是當前編碼設定下的字節碼串兒,典型的本末倒置。在 py3 中,再也不有 u"xxx" 的語法,"xxx" 定義的就是字符串(unicode編碼),b"xxx" 定義的纔是字節碼串兒。
""" py3 中消除了py2中字節碼和字符串混亂的定義 str 就是字符串 bytes 纔是字節碼 在 py2 中 str 是字節碼 unicode 纔是字符串 """ py3 py2 字節碼 bytes str 字符串 str unicode
因此在 py3 中,你的 coding: utf-8 的註釋沒有任何用處了。在 py2 中它會隱式的根據指定編碼對 str 作字節碼的定義,但在 py3 中已經沒有這種隱式的轉換和定義了。
import sys # 默認字符集由 ascii 改成 utf-8 print("默認字符集爲:%s" % (sys.getdefaultencoding())) str = "你好" # 2 個字符長度 py2 中就不會把 str 看爲數據結構 而是字節碼 因此結果會是 6 print(len(str)) # 不指定字符集將使用默認的 utf-8 進行編碼 str.encode() # b'\xe4\xbd\xa0\xe5\xa5\xbd' str.encode("utf-8") # b'\xe4\xbd\xa0\xe5\xa5\xbd' str.encode("gb2312") # b'\xc4\xe3\xba\xc3' hello_utf8 = b'\xe4\xbd\xa0\xe5\xa5\xbd' hello_gb2312 = b'\xc4\xe3\xba\xc3' # utf-8 轉 gb2312 / gb2312 轉 utf-8 # 都是以 unicode 做爲基準進行操做 hello_utf8.decode("utf-8").encode("gb2312") hello_gb2312.decode("gb2312").encode("utf-8")
py 以 unicode 做爲字符運算的基準。
bytes -- decode -- unicode -- encode -- bytes
unicode 編碼 至其餘字符集(utf-8/gb2312),其餘字符集 解碼 至 unicode
在 py2 中常常遇到的錯誤:
"你好".encode("utf-8")
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc4 in position 0: ordinal not in range(128)
錯誤的緣由是什麼呢?py2 以 ascii 做爲默認字符集,"你好" 直接 encode 至其餘編碼時會進行一次隱式的解碼。由於 py 以 unicode 做爲運算基準,因此會先 decode 至 unicode,你沒使用 py 就隱式執行,使用默認字符集 ascii 作解碼,天然沒法解碼中文字符的字節碼。
而在 py3 中是不容許這種操做的。從 bytes 到另外一 bytes 你必須先 decode 後才能 encode。
py2 中,open 方法並不會設定以何種字符集去打開文件,由於 py 要的不是字符串,而是字節碼串兒。是的,read 和 write 操做的都是字節碼串兒,字節碼串兒自己並不具有表徵字符的能力,咱們須要手動的指定正確的字符集進行解碼。
# write.py # -*- coding: utf-8 -*- with open("hello.txt", "w") as f: f.write("你好") # 這裏實際上是字節碼 # read.py # -*- coding: gbk -*- # 結果爲 6 仍然是以 utf-8 的字節碼去計算的 with open("hello.txt", "r") as f: str = f.read() print len(str) # 讀取的也是字節碼
數據會以相應的字節碼去保存
py3 中,由於存儲時傳入的是 unicode 字符串(而再也不是 py2 的字節碼),但數據存儲確定是以字節碼的形式(物理存儲)。因此 py3 確定會將字符串轉爲字節碼(這個過程是py3本身完成的,咱們此時但是指定以何種字符集去編碼),這時就涉及採用何種編碼去進行隱式的 encode 存儲和 decode 讀取了。
中文系統,使用 python xxx.py 時上下文的編碼爲操做系統的編碼:gbk。這裏挺饒的,雖然我是在 gbk2312 的 cmd 裏運行的,但並不是是交互模式,因此輸入時並不是使用的 cmd 的字符集,而是 python 解釋器啓動時讀取的本地系統的字符集:gbk。
#-*-coding: utf-8 -*- """ py3 的默認字符集爲 utf-8 因此平常開發中咱們是能夠省略 encoding 的 """ with open("hello.txt", "w", encoding = "utf-8") as f: f.write("你好") with open("hello.txt", "r", encoding = "utf-8") as f: print(f.read()) # -*-coding: utf-8 -*- 就算我聲明瞭此行註釋 # py3 依然不會使用其做爲 open 打開文件時默認的 encoding # 也不會使用 py3 自身默認的 sys.getdefaultencoding # 這裏 open 的 encoding 默認會與你的本地系統相關 # 因此下面我須要使用 gbk 返回字節碼,從新使用 utf-8 正確解碼 with open("hello.txt", "r") as f: print(f.read().encode("gbk").decode('utf-8')) # # 以 b(二進制) 方式讀取時爲讀取的字節碼串兒 # 因此數據並不會受編碼影響 # 咱們使用時須要指定正確的解碼字符集 with open("hello.txt", "rb") as f: bytes = f.read() print(bytes) print(len(bytes)) # 6 utf-8 的字節碼 一箇中文3個字節 print(bytes.decode("utf-8")) #正常解碼 得到 "你好" unicode 字符串 # bytes.decode("gb2312") #錯誤解碼
因此,coding: utf-8 貌似已經沒什麼用處了