由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,就到这里。

 

posted @ 2018-08-29 17:24  快乐的凡人721  阅读(1257)  评论(3编辑  收藏  举报