烦恼一般都是想太多了。

0%

Python2和3中字符串与编码

说实话,本来是非常不想用 Python2 的,但是架不住服务器上大多都是自带的 Python2,而且还是内网环境,想要到处部署 Python3 的话,再加上装依赖的话,是比较麻烦的。所以不得不用。但是 Python2 的字符集处理也是非常让人头疼的内容。

字符集与编码

字符集,指的是一组符合的集合。在 MFC中的字符宏与CString 已有很多说明了。

但是我们常说到一些字符集或者编码的时候,由于其编码和字符集是一个名称,所以常常会因此而混淆。

字符集中,每个符号都会有一个整数表示的,这个整数,我们叫它为字符的 码点,将符号用一个整数码点来表示,这一过程叫做字符编码。

不同的字符集中,相同的符号,拥有不同的 码点

对于计算机来说,其实他不认识符号,所以我们的符号最终都是,通过存储在计算机的字节中来表示的。

因此,对于一个符号,想要让计算机正确的进行存储和表示,我们需要指定一个符号采用哪一个字符集编码。

比如说对汉字 ,其 Unicode 编码是 4e2d,而在 GBK 字符集中,却是 d6d0

所以,如果我们不对符号指定正确的字符集,那么肯定就会出错了。

Unicode 与 UTF_8

Unicode 是对字符到整数代码的编码表示,而 UTF-8,则是如何将这个整数编码在计算机进行存储,理解这点很重要。

Python2

在 Python2 中,默认使用的是 ASCII 编码,而是,Python2 中的字符串实际上是 bytes ,字节序列。

提前注意:

  • repr()__repr__() 实际上打印出来的是,计算机进行识别和表示的内容
  • str(),__str__() 实际上是打印出来的是,人类可读的形式。

因此,我们就来探究一下这个问题。我们一个字 来表示。

s = '中'
repr(s)
"'\\xe4\\xb8\\xad'"

其实我开始也在疑问,为什么默认编码是 ASCII 的情况下,不报多,而是显示三个字节的内容呢?

实际上这三个字节的内容,正是中的 UTF-8 编码,我怀疑是因为在 ASCII 无法识别符号的情况下,根据我的环境变量来将其解释为 utf-8 的。

某个字符想要转换成另外一个种字符集中的编码,是不能直接进行的,必须先转换成 Unicode 码点,然后再转换到其他编码:

s.decode('utf-8').encode('gbk')
'\xd6\xd0'

再次重申一点,对于 Python2 ,默认是 ASCII 编码,当遇到识别不了的的符号时,在交互式运行解释器的情况下,可能会应用系统的编码来处理。之所以这样说,是因为如果我们以脚本的形式执行的话,如果不指定编码,就会报错。

对一个符号,实际上 Python2 已经用他自己认为的方式,转换成了字节序列存在在内存中,我们必须首先将其转换成 Unicode 码点,再进行转换到其他编码。

Python3

Python3 有所不同,所有的符号都存储为 Unicode 码点,因此,他已经不再拥有 decode 方法,而只有 encode 方法。

s = '中'
repr(s)
"'中'"

计算机已经能直接识别这个符号了。这个时候,字符的操作和处理方式,就没有 Python2 这么繁琐了。但我们同样看出他存储的内容是什么:

hex(ord(s))
'0x4e2d'

ord 用来取出一个字符在计算机内的存储 Unicode 码点。在 Python2 中这个只能接受单字符(因为他是 ASCII 字节序列存储的),而 Python3 就随便搞了(所有的 Unicode 符号都可以)。

将符号转换为 ASCII 进行保存。

比如,我们在传输数据的时候,为了减少麻烦,做一下简单的加密。就会有字符串,处理成 ASCII 的格式在 HTTP 上进行传输。Python2 和 Python3 的实现就会有所不同。

现在,我们约定,我们使用的中文是 GBK 编码。

Python2

这里,我们的中文,将会使用 ‘utf-8’ 进行编码,存储在字节序列中,我们要先将其转换成 GBK 编码的序列

s = '中'
bs = s.decode('utf-8').encode('gbk') # 先转 Unicode 码点,再转换成 GBK 的码点
bs
type(bs) # bs 还是 str,不是 bytes,但是 str 却是 bytes
isinstance(bs,bytes)
'\xd6\xd0'
''.join(['%02x' % (ord(ss)) for ss in bytess]) # 所以这里我们需要用 ord 来将 str 转换成整数

Python3

s = '中'
bs = s.encode('gbk')
type(bs)
isinstance(bs,bytes)
''.join(['%02x' % ss for ss in bs]) # 字节序列,直接可以用

这也验证了,在 Python2 中,其实 str 就是字节序列。而 Python3 中,str 与 bytes 是不同的

Unicode 码点

对于一个中文符号,其实我们可以查看其在 Python3 的内部是如何存储的:

s = '中'
ord(s)
20013

一个整型值。这与所有符号都是有一个码点来编码的一致。

字面值

字符串和字节串字面值

对于字符串,我们经常会见到很多不同开头的前缀。这代表的是 字面值 的意思,包括字符串字面值和字节串字面值。

字符串字面值前缀:

"r" | "u" | "R" | "U" | "f" | "F"
| "fr" | "Fr" | "fR" | "FR" | "rf" | "rF" | "Rf" | "RF"

字节串字面值前缀:

"b" | "B" | "br" | "Br" | "bR" | "BR" | "rb" | "rB" | "Rb" | "RB"

实际上就是这么几个:

  • b,B 表示是字节串。只能是 ASCII 的字符
  • r,R 原始字符串,其中的反斜杠不会被转义
  • u,U Unicode 字节串
  • f,F 格式化字符串。可以 r,R 连用,但是不能与 b,B,u,U 连用

整字字面值

  • 0{ b | B | o | O | x | X} 二进制,8进制,16进制值
  • 77e10 这种是浮点字面值

内置函数

int

从字符串构建整数。

int('0x77',16)
119

bin

将整数转换为二进制字符串

bin(118)
'0b1110110'

注意,这个时候已经是字符串了

hex

将整数转换成 16 进制字符串:

hex(12)
'0xc'

位操作

位操作之前我还纠结了很久,其实直接用整型进行操作就行了。

缘由是我们要和一个打印机通讯获取其状态,不过通过 SNMP 获取的他的状态,有的时候是 0x00 这样表示正常,但是返回的的异常状态却可能会是 A,B 这样的情况,是将一个16进制的值,解释成了字符串。返回的是一个16bit,2字节的值,每一位代表一个信息位。因此,最终我研究出来的办法是,先将此字符串,转换成其代表的值,然后进行相关的与或操作。

st = snmp.get() # 位代码
mask = 0b00000001
sti = ord(st)
sti & mask
sti & mask << 1