由time.tzname返回值引發的對str、bytes轉換時編碼問題實踐

Windows 10家庭中文版,Python 3.6.4,

 

下午複習了一下time模塊,熟悉一下其中的各種時間格式的轉換:時間戳浮點數、struct_tm、字符串,還算順利。

可是,測試其中的time.tzname屬性時遇到了亂碼,如下:

1 >>> import time
2 >>> time.tzname
3 ('Öйú±ê׼ʱ¼ä', 'ÖйúÏÄÁîʱ')

返回了一個元組,可是,亂碼怎麼看得懂!

 

補充:time.tzname

A tuple of two strings: the first is the name of the local non-DST timezone, the second is the name of the local DST timezone.

 

從結果來看,返回的是兩個Unicode字符串組成的元組。

那麼,這兩個字符串用的是什麼編碼呢?怎麼轉換爲孤可以讀的懂得信息呢?

網上搜索到一篇文章(https://www.oschina.net/question/2927993_2199064?sort=default),解決方法爲:

1 a = time.tzname[0]
2 b = a.encode('latin-1').decode('gbk')
3 print(b)

說明,後面的gbk更改爲gb2312也是可以的。

測試的b:

中國標準時間

 

上面代碼解釋(參考鏈接1中會解釋的更清楚):

使用encode將字符串轉換爲bytes,再使用decode將bytes轉換爲字符串,最後得到一個gbk編碼的字符串,此字符串在Python IDLE就可以正常顯示了。

 

能看懂了。可是,爲什麼要做這樣的轉換呢?爲何是latin-1、gbk呢?繼續dig

 

補充:

除了使用encode、decode實現str、bytes轉換外,還可以使用str()、bytes()來執行兩者的轉換,下面會用到。

 

補充:

str.encode(encoding="utf-8", errors="strict")
Return an encoded version of the string as a bytes object. Default encoding is 'utf-8'.

bytes.decode(encoding="utf-8", errors="strict")
bytearray.decode(encoding="utf-8", errors="strict")
Return a string decoded from the given bytes. Default encoding is 'utf-8'.

 

疑問:怎麼判斷字符串用的什麼編碼方式呢?

字符串,可以認爲是字符組成的數組,那麼,獲取每個字符串中的字符在內存中的表示如何?是什麼樣的整數?當然,Python中是沒有單純的字符的,都是字符串。

在參考鏈接2中,找到了 將字符轉換爲整數的函數——ord

下面是使用

>>> tzname = time.tzname
>>> for ch in tzname[0]:
    print("0x%x" % ord(ch))

    
0xd6
0xd0
0xb9
0xfa
0xb1
0xea
0xd7
0xbc
0xca
0xb1
0xbc
0xe4

 

遺憾的是,由於自己水平有限,無法根據上面的信息使用的是何種編碼方式。

 

下面是更進一步測試

item = time.tzname[0]

 

Test 1:

bsx0 = bytes(item, encoding="gbk")

發生異常:

UnicodeEncodeError: 'gbk' codec can't encode character '\xd6' in position 0: illegal multibyte sequence

 

 

看來上面的做法是不對的。上面的bytes()函數類似於encode的功能——字符串str 轉 bytes。

 

Test 2:

bs0 = bytes(item, encoding="utf-8")
print(bs0)
print(chardet.detect(bs0))
print("OK 1? ", str(bs0, 'utf-8'))
print('0x%x' % ord(str(bs0, 'utf-8')[0]))

測試結果:

b'\xc3\x96\xc3\x90\xc2\xb9\xc3\xba\xc2\xb1\xc3\xaa\xc3\x97\xc2\xbc\xc3\x8a\xc2\xb1\xc2\xbc\xc3\xa4'
{'encoding': 'utf-8', 'confidence': 0.99, 'language': ''} 使用chardet.detect檢測到的編碼類型
OK 1? Öйú±ê׼ʱ¼ä 還是亂碼,和IDLE中一樣
0xd6 先執行bytes、再執行str,兩次都用utf-8,結果,得到的第一個字符的十六進制仍然是0Xd6

 

Test 3:

bs = bytes(item, encoding="latin-1")
print(bs)
print(chardet.detect(bs))
str_bs = str(bs, 'gbk')
print(str_bs)
print('0x%x' % ord(str_bs[0]))

print(bytes(str_bs, encoding='gbk'))

測試結果:

b'\xd6\xd0\xb9\xfa\xb1\xea\xd7\xbc\xca\xb1\xbc\xe4' 字符串轉bytes時使用了latin-1,得到的編碼,和打印每個字符的16進制的結果一致
{'encoding': 'GB2312', 'confidence': 0.99, 'language': 'Chinese'} 可是,使用chardet.detect檢測到的編碼居然是GB2312
中國標準時間 bytes使用gbk(gb2312也可以)轉換爲str後輸出的結果,好了,不是亂碼了
0x4e2d 查看上面的字符串的第一個字符的十六進制數值,這次不一樣了,

b'\xd6\xd0\xb9\xfa\xb1\xea\xd7\xbc\xca\xb1\xbc\xe4' 使用gbk編碼得到的bytes,和前面使用latin-1編碼得到的bytes一樣啊!

 

Test 4:

繼續上面的Test 3進行測試:str_bs是上面使用gbk轉換後得到的字符串

 

bs2 = bytes(str_bs, encoding='utf-8')
print(bs2)
print(chardet.detect(bs2))
print("OK 2? ", str(bs2, 'utf-8'))
print('0x%x' % ord(str(bs2, 'utf-8')[0]))

測試結果:

b'\xe4\xb8\xad\xe5\x9b\xbd\xe6\xa0\x87\xe5\x87\x86\xe6\x97\xb6\xe9\x97\xb4' 將編碼爲gbk字符串用utf-8轉換爲bytes,結果和Test 3中得到的不一樣,
{'encoding': 'utf-8', 'confidence': 0.99, 'language': ''} 檢測到編碼爲utf-8,
OK 2? 中國標準時間 也顯示了看得懂的字符串
0x4e2d 第一個字符的十六進制,

 

 

疑問

問題在哪兒呢?爲何孤要將字符串轉換爲UTF-8呢?

 

Unicode字符編碼、UTF-8、GBK、GB2312到底有什麼關係呢?

 

b'\xd6\xd0\xb9\xfa\xb1\xea\xd7\xbc\xca\xb1\xbc\xe4'

怎麼轉換爲:

b'\xe4\xb8\xad\xe5\x9b\xbd\xe6\xa0\x87\xe5\x87\x86\xe6\x97\xb6\xe9\x97\xb4'

計算方法是什麼?

 

Test 5:

new_item = item.encode('latin-1').decode('gbk')
print('OK 3?', new_item)
print('0x%x' % ord(new_item[0]))

new_item2 = new_item.encode('gbk').decode('utf8')
print(new_item2)

測試結果:

OK 3? 中國標準時間
0x4e2d
Traceback (most recent call last):
File "D:\eclipse\workspace\zl0425\src\test\aug\time01.py", line 52, in <module>
new_item2 = new_item.encode('gbk').decode('utf8')
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xd6 in position 0: invalid continuation byte 出錯了!

 

疑惑:

在Test 3中,原始字符串 使用latin-1轉換爲bytes 再使用gbk轉化爲字符串;

在Test 4中,將Test 3得到的gbk轉化來的字符串使用utf-8轉換爲bytes 再用utf-8轉換爲字符串;

latin-1 --> gbk -->utf-8,沒有出錯,可在Test 5中使用encode、decode時出錯了呢?

 

將出錯語句中的gbk更改爲utf8,結果,new_item2中顯示正常了:

new_item2 = new_item.encode('utf8').decode('utf8')

結果:

中國標準時間

 

1723 還是不完全明白,晚點再看看

1805 開機,電量滿滿的,再戰此問題

 

看過參考鏈接4、5,並對漢字“漢”做了一些實驗,發現,無論是 encode還是decode,都是對內存中的字節進行操作

下面是使用bytes()、str()函數進行測試的結果:

# 本身就是Unicode字符
>>> han = ''
# 輸出 漢 在Unicode字符集中的 編碼
>>> ord(han)
27721
>>> print('0x%x' % ord(han))
0x6c49


# 使用latin-1將Unicode字符——大於255——轉換爲bytes:異常,無法解析
# Unicode字符小於256時是可以的!
>>> bytes(han, encoding='latin-1')
Traceback (most recent call last):
  File "<pyshell#63>", line 1, in <module>
    bytes(han, encoding='latin-1')
UnicodeEncodeError: 'latin-1' codec can't encode character '\u6c49' in position 0: ordinal not in range(256)

# Unicode字符 使用 utf-8轉換爲bytes,成功
# 用什麼解碼,就用什麼進行編碼
>>> hanbs = bytes(han, encoding='utf-8')
# 三個字節的UTF-8編碼
>>> hanbs
b'\xe6\xb1\x89'

# 將UTF-8編碼得到的字節使用latin-1編碼轉換爲字符串
# 是按照上面的每個直接進行處理,結果得到一個長度爲3的字符串,存在看不懂的亂碼
# \xe6、\xb1、\x89分別代表一個字符
# 此時是Unicode字符,但小於256
>>> han_latin = str(hanbs, 'latin-1')
>>> han_latin
'æ±\x89'

# 將latin-1編碼的字符串使用utf-8轉換爲bytes
# 每個字符一個轉換,三個字符就是三個轉換
# 結果得到下面的bytes——utf-8編碼的bytes,此時有6個字節了
# 每兩個字節代表一個 前面han_latin中的一個字符(Unicode字符)
>>> hanbs2 = bytes(han_latin, encoding='utf-8')
>>> hanbs2
b'\xc3\xa6\xc2\xb1\xc2\x89'
>>> str(hanbs2, encoding='utf-8')
'æ±\x89'

# 還是用latin-1編碼將latin-1編碼的字符串轉換爲bytes吧
# 在把bytes轉換爲utf-8編碼的字符串,又恢復了“漢”
>>> hanbs3 = bytes(han_latin, encoding='latin-1')
>>> hanbs3
b'\xe6\xb1\x89'
>>> str(hanbs3, encoding='utf-8')
''


# 對字母a進行測試
>>> zimu = 'a'
>>> ord(zimu)
97
>>> print('0x%x' % ord(zimu))
0x61

>>> zimubs = bytes(zimu, encoding='utf-8')
>>> zimubs
b'a'
>>> zimu_latin = str(zimubs, 'latin-1')
>>> zimu_latin
'a'

# 無論如何轉換,得到的bytes都是b'a',
# 因爲latin-1、utf-8編碼對小於256的是兼容的——相同
>>> bytes(zimu_latin, 'utf-8')
b'a'

 

上面的測試做完後,發現自己對用不同編碼方式進行 編碼、解碼 的原理是瞭解了。

 

那麼,爲何在編解碼中會發生錯誤呢?

存儲在內存中的字節數組的 不符合相應 的 編碼方式 的編碼規則,比如,超過其範圍了,這樣的話,異常就發生了。

不只是bytes()、str()函數會報錯,encode()、decode()函數也會報錯。其實,兩套函數的功能是相同的(需要試驗證明)。

 

好了,不用爲字符集編碼什麼的發愁了,再次面對時,將充滿自信,嘿~ 

 

參考鏈接:

1.Python中的str與bytes

2.python中的字符數字之間的轉換函數

3.Python | 多種編碼文件(中文)亂碼問題解決

4.編碼方式的簡介(ASCII, LATIN-1, UTF-8/16/32)

5.ISO-8859-1 、Latin-1 西歐編碼介紹及應用

 

0830 0954-更新參考鏈接:

6.網頁編碼就是那點事

7.Java| 編碼格式介紹(ANSI、GBK、GB2312、UTF-8、GB18030和 UNICODE)

8.徹底搞懂字符編碼(unicode,mbcs,utf-8,utf-16,utf-32,big endian,little endian...)

9.windows內碼、外碼、字符映射表

10.UTF-8和UTF-16使用對比

 

總結:

閱讀完更新的鏈接後,發現不同字符集編碼的字符之間的轉換的成本是很高的,,比如,Unicode中的漢字 轉換爲 GB18030中的漢字,需要查找它們各自碼錶的對應值,或許有人在做這個工作,或許沒有人做。

GB2312原來在1981年5月就開始實施了啊!真早!中國國家標準總局 制定的。

之後,GBK包含GB2312,之後,GB18030包含GBK,GB18030-2005包含G18030-2000,,之後沒有更多更新了。

之後就是萬國碼Unicode了!最新版本Unicode 11.0:2018年6月5日(來自百度百科 Unicode 詞條)!

Unicode和GB*字符集是不兼容的,轉換成本很高,或許有現成軟件,也可能沒有。

 

至於UTF-8,是Unicode字符集的 網絡傳輸編碼方式。問題,爲何要用UTF-8呢?根據參考鏈接10的介紹,有兩個原因:

1.傳輸效率,可以節約傳輸的流量——變長設計

2.避免網絡傳輸時字節破壞的問題

至於UTF-16,Java在內存裏面使用其編碼,但網絡傳輸時,使用UTF-8。

 

Ok,就到這裏。

 

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