徹底搞懂py2.x/py3.x中編碼和解碼問題

結論

1.  字符在內存中都是以unicode類型存在。

2.  當數據要保存到磁盤或者網絡傳輸時,會轉爲utf-8等編碼再保存或傳輸。

3.  python文件第一行指定的編碼方式用於向python解釋器指出解碼方式。

4.  Python中字符的存儲類型有bytesunicode,py2.x中被稱爲strunicode, py3.x中被稱爲bytesstr.

5.  Py2.xpy3.x中字符的類型都爲str, 因此在py2.x中是bytes類型,在py3.x中是unicode類型。

6.  py2.x中文件默認解碼方式(即python文件第一行不指定時)爲ASCII, py3.x中爲UTF-8. 使用sys.getdefaultencoding()獲得。

7.  編碼、解碼過程。

image.png

image.png

8.  IDE在程序運行前先按文檔屬性設定的編碼方式把數據保存到磁盤,然後python解釋器按文件第一行的解碼方式把磁盤中存儲的二進制序列讀取並解碼爲unicode加載到內存中。


image.png

概念

編碼:把人類發明的文字及符號轉化爲計算機能夠識別的二進制序列的過程。

解碼:把計算機內部存儲的二進制序列轉化爲人類能夠認識的文字及符號的過程。

ASCII: 佔一個字節,保留最高位,其餘7位組成了128個字符的字符集。

unicode: unicode編碼了世界上所有的文字。

utf-8: unicode進行了壓縮和優化。ASCII碼中的內容用1個字節保存、歐洲的字符用2個字節保存,東亞的字符用3個字節保存。

GBK: 漢字的國標碼,用2個字節保存。

 

py2.x

1.  驗證Py2.x中的字符類型。

Py2.x:
#coding:utf-8
s = '中國hello'
print s
print type(s)
print len(s)
print repr(s)
執行結果:
中國hello
<type 'str'>
11
'\xe4\xb8\xad\xe5\x9b\xbdhello'

可見是str類型,即bytes類型。len()是佔用的字節數。

Py2.x:
#coding:utf-8
s = u'中國hello'
print s
print type(s)
print len(s)
print repr(s)
執行結果:
中國hello
<type 'unicode'>
7
u'\u4e2d\u56fdhello'

指定了使用unicode類型。 u4e2dunicode字符集中字符“中”的代碼。len()是字符的個數。

2.  bytesunicode的轉換。

#coding:utf-8
s = '中國'
print type(s)
print len(s)
 
s2 = s.decode('utf-8')
type(s2)
print len(s2)
執行結果:
<type 'str'>
6
<type 'unicode'>
2

3.  不同編碼類型的字符串拼接。

Py2.x:
#coding:utf-8
print 'cisco'+u'google'
執行結果:
ciscogoogle
之所以英文字符可以把兩種類型的進行拼接,是因爲在python2.x中,只要數據全部是 ASCII 的話,python解釋器自動把 byte 轉換爲 unicode 。但是一旦一個非 ASCII 字符偷偷進入你的程序,那麼默認的解碼將會失效,從而造成 UnicodeDecodeError 的錯誤。python2.x編碼讓程序在處理 ASCII 的時候更加簡單。你付出的代價就是在處理非 ASCII 的時候將會失敗。
Py2.x:
#coding:utf-8
s = 'hello'+'china'
print s
print type(s)
print repr(s)
執行結果:
hellochina
<type 'str'>
'hellochina'
查看不同編碼類型拼接後的存儲類型:
Py2.x:
#coding:utf-8
s = 'hello'+u'china'
print s
print type(s)
print repr(s)
執行結果:
hellochina
<type 'unicode'>
u'hellochina'
可見Py2.x進行了自動轉換。
Py2.x:
#coding:utf-8
print '中國'+'美國'
執行結果:
中國美國
Py2.x:
#coding:utf-8
print '中國'+u'美國'
執行結果:
Traceback (most recent call last):
  File "E:\python\study\test\index.py", line 14, in <module>
    print '中國'+u'美國'
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)

Py3.x中即使都是ASCII範圍內,也不能進行拼接:

#coding:utf-8
s1 = 'cisco'
print(type(s1))
s2 = b'google'
print(type(s2))
print(s1+s2)
執行結果:
<class 'str'>
<class 'bytes'>
Traceback (most recent call last):
  File "E:\python\study\test\index.py", line 6, in <module>
    print(s1+s2)
TypeError: can only concatenate str (not "bytes") to str

py3.x

1. 驗證Py3.x中的字符類型。

#coding:utf-8
import json
s1 = '中國'
print(type(s1))
print(len(s1))
print(json.dumps(s1))
print(s1)
 
s2 = s1.encode('utf-8')
print(type(s2))
print(len(s2))
print(s2)
執行結果:
<class 'str'>
2
"\u4e2d\u56fd"
中國
<class 'bytes'>
6
b'\xe4\xb8\xad\xe5\x9b\xbd'
#coding:utf-8
s = u'中'
print(s)
print(type(s))
print(len(s))
print(repr(s))
print(ord(s))
print(bin(ord(s)))
 
中
<class 'str'>
1
'中'
20013
0b100111000101101

2.  bytesunicode的轉換。除了encodedecode的轉換方法,還可以:

#coding:utf-8
s1 = '中國'
print(type(s1))
 
s2 = bytes(s1,encoding='utf-8')
print(type(s2))
 
s3 = str(s2,encoding='utf-8')
print(type(s3))
執行結果:
<class 'str'>
<class 'bytes'>
<class 'str'>

常見錯誤

1.  py2.x中默認的解碼方式是ASCII, py3.x中默認的是utf-8, 當在py2.x中指定解碼方式爲utf-8時,py2.xpy3.x應該是沒有區別,可爲何在py2.x中默認的字符類型是bytes, 而在py3.x中確是unicode, 都是utf-8,不應該都是bytes嗎?或者既然都加載在內存了,不該都是unicode嗎?

答:python解釋器從磁盤讀取文件,以unicode編碼方式把整個代碼加載到內存中,然後逐條執行,當識別到字符串時,py2.x默認的str類型是bytes, py3.x默認的str類型是unicode, 但當有明確指定字符類型時,按指定的編碼,如py2.x中的u'china', py3.x中的b'google'.

2.  Py2.x中不指定utf-8編碼方式時,print漢字會報錯:

Py2.x:
s = '中國'
print s
結果:
File "E:\python\study\test\index.py", line 3
SyntaxError: Non-ASCII character '\xe4' in file E:\python\study\test\index.py on line 3, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details

這是因爲py2.x中默認的解碼方式爲ASCII, 而文件保存的編碼方式爲UTF-8, 兩者不匹配,因此報錯。

3. Py2.x中字符串s本來應該是字節類型,但爲何print時卻顯示爲明文了呢?

Py2.x中:
>>> s = '中國'
>>> s
'\xd6\xd0\xb9\xfa'
>>> print s
中國
>>>print '\xd6\xd0\xb9\xfa'
中國

這是因爲print在執行時調用了str函數,str函數執行了bytesunicode的操作。但py3.x中不存在這種現象:

#coding:utf-8
import json
s1 = '中國'
print(type(s1))
print(len(s1))
print(json.dumps(s1))
print(s1)
 
s2 = s1.encode('utf-8')
print(type(s2))
print(len(s2))
print(s2)
執行結果:
<class 'str'>
2
"\u4e2d\u56fd"
中國
<class 'bytes'>
6
b'\xe4\xb8\xad\xe5\x9b\xbd'
控制檯中:
>>> s = '中國'
>>> print(type(s))
<class 'str'>
>>> s
'中國'
>>> print(s)
中國
>>> s1 = bytes(s,encoding='gbk')
>>> s1
b'\xd6\xd0\xb9\xfa'
>>> print(s1)
b'\xd6\xd0\xb9\xfa'
>>>

4.  utf-8保存文件,在windows中執行,輸出不同:

#coding:utf-8
s = '中國'
print(s)
 
D:\Python37-32>python d:\index.py
中國
D:\Python27>python d:\index.py
涓浗

因爲py3.x中字符串被識別爲unicode, 傳給cmd.exe時被編碼爲GBK,再以GBK解碼輸出。但py2.x中字符串被識別爲bytes, utf-8編碼的兩個漢字有6個字節,傳給cmd.exe時按GBK解碼,識別成爲了3個亂碼,再以GBK解碼輸出。

5.  Py3.x使用openr方法打開utf-8編碼的文件時會報錯:

#coding:utf-8
 
f = open('index.py','r')
print(f.read())
執行結果:
Traceback (most recent call last):
  File "E:\python\study\test\test.py", line 5, in <module>
    print(f.read())
UnicodeDecodeError: 'gbk' codec can't decode byte 0xad in position 2: illegal multibyte sequence
使用rb時輸出:
b'\xe4\xb8\xad\xe5\x9b\xbd'

py2.x中不會報錯。open()方法打開文件時,read()讀取的是str(py2.x中即是bytes),讀取後需要使用正確的編碼格式進行decode().

Py2.x:
f = open('index.py','r')
s = f.read()
print type(s)
print len(s)
print s
執行結果:
<type 'str'>
6
中國
可見,此處使用utf-8進行解碼,如果指定爲GBK呢?
#coding:gbk
f = open('index.py','r')
s = f.read()
print s
輸出是涓浗,改爲ASCII後同樣報錯。

可查看py3.x使用的是GBK進行解碼:

Py3.x:
>>> f = open('index.py','r')
>>> f
<_io.TextIOWrapper name='index.py' mode='r' encoding='cp936'>
但py2.x沒有顯示編碼方式:
>>> f = open(r'e:\python\study\test\index.py','r')
>>> s = f.read()
>>> f
<open file 'e:\\python\\study\\test\\index.py', mode 'r' at 0x016BD1D8>

可使用open('index.py','r',encoding='utf-8')指定編碼方式。

參考資料

https://www.cnblogs.com/OldJack/p/6658779.html

http://www.cnblogs.com/yuanchenqi/articles/5938733.html

https://www.cnblogs.com/shine-lee/p/4504559.html

https://blog.csdn.net/nyyjs/article/details/56667626

https://blog.csdn.net/nyyjs/article/details/56670080

https://www.jianshu.com/p/19c74e76ee0a

https://www.jb51.net/article/59599.htm


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章