Python 关于编码、X 进制、Python3 字符串的那些事儿

MrCharp · 2019年04月16日 · 最后由 阿东 回复于 2019年04月16日 · 3403 次阅读

1.引言

好早就计划要分享点什么东西,一直在想分享点什么,恰巧最近我发现自己也是搞晕了字符串的一些知识,就想干脆分享点字符串的内容吧,也算是对自己最近一段时间知识结合的一个总结吧。看了些参考资料,对于这块的总结不是长篇大论就是过于简短。因此打算就自己的理解还是写点内容吧。

2.正文

2.1 字符编码

ASCII 编码

ASCII 码就是一个字节。ASCII 编码首先是一种单字节 (Byte)(即 1Byte = 8 bit) 编码,最多有 $28$(256) 个组合,一个二进制数字序列,在计算机中作为一个数字单元,一般为 8 位二进制数。换算为十进制 ,最小值-128,最大值 127,一个整数表示一个字元,如 65 就是 A。

Unicode 编码

但是 Ascii 编码并不能显示其他字符,比如中文汉字有上万个,显然在短短 256 个组合中,在 ASCII 编码中无法展示。Unicode(中文:万国码、国际码、统一码、单一码)是计算机科学领域里的一项业界标准。它对世界上大部分的文字系统进行了整理、编码,使得计算机可以用更为简单的方式来呈现和处理文字。最常用的是用两个字节表示一个字符(也有可能超过 2 个字节表示 1 个字符)。
那么 ASCII 中的"a"用十进制表示 65,二进制表示则是 0100 0001。

字符 1 个字节表示 (ASCII) 二进制 2 个字节表示 (Unicode) 二进制 十进制
a 01000001 00000000 01000001 65
- 11000100 0010001 25105

UTF-8 编码

UTF-8(8-bit Unicode Transformation Format)是一种针对 Unicode 的可变长度字符编码,也是一种前缀码。它可以用来表示 Unicode 标准中的任何字符,且其编码中的第一个字节仍与 ASCII 兼容,这使得原来处理 ASCII 字符的软件无须或只须做少部分修改,即可继续使用。因此,它逐渐成为电子邮件、网页及其他存储或发送文字优先采用的编码。

utf8 的编码规则:

1、对于单字节的符号, 字节的第一位设为 0, 后面 7 位为这个符号的 unicode 码. 因此对于英语字母, UTF-8 编码和 ASCII 码是同样的;

2、对于 n 字节的符号 (n>1), 第一个字节的前 n 位都设为 1, 第 n+1 位设为 0, 后面字节的前两位一律设为 10. 剩下的没有提及的二进制位, 所有为这个符号的 unicode 码。

N 个字节 Unicode 符号范围 (十六进制) UTF-8 编码方式 (二进制)
1 0000 0000 - 0000 007F 0xxxxxxx
2 0000 0080 - 0000 07FF 110xxxxx 10xxxxxx
3 0000 0800 - 0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx
4 0001 0000 - 0010 FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
5 0020 0000 - 03FF FFFF 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
6 0400 0000 - 7FFF FFFF 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

Unicode 编码都可以使用 UTF-8 进行编码——这种编码对前 127 个字元,每个字符串都可以都可以使用一个字节来表示。如:
已知 a 为 ascii 码,因此,在第 1 行,直接转为 01000001;
已知"我"unicode 为 25105(10 进制),转为 16 进制为 6211(16 进制),那么应该为第 3 行,因此"我"的 UTF-8 编码须要三个字节, 即格式是
"1110xxxx 10xxxxxx 10xxxxxx". 然后, 从"我"的最后一个二进制位开始, 依次从后向前填入格式中的 x, 多出的位补 0. 这样就得到了, "我"的 UTF-8 编码是 "11100110 10001000 10010001", 转换成十六进制就是 e686b7。

字符 1 个字节表示 (ASCII) 二进制 2 个字节表示 (Unicode) 二进制 十进制 UTF-8
a 01000001 00000000 01000001 65 01000001
- 110001000010001 25105 11100110 10001000 10010001

显然,对英文文本而言,UTF8 是非常紧凑的,只使用了 1 个字节来表示。


实际上,在计算机内存中统一使用 Unicode 编码,当保存到硬盘或网络传输时,为节约硬盘和网络传输,转换为了 UTF-8 或其他指定编码。

2.2 编码与解码

编码与解码

我们知道编码与解码是相对的。比如 a 通过 ascii 编码转为 01000001;我们使用 ascii 解码 01000001 解码出来的就是 a。

编码:把真实字符按照某种编码规则(比如 Unicode)转为计算机语言。

解码:即将计算机语言转为真实字符的过程。

因此,编码和解码一定要采用相同的编解码规则。

print("我".encode('UTF8'))
print(b'\xe6\x88\x91'.decode('UTF8'))
# print(b'\xe6\x88\x91'.decode('UTF16')) # 显然通过utf8编码的通过utf16无法解码。
# print("我".encode('ascii')) # huiabaoc

b'\xe6\x88\x91'

通过以上的例子可以明显看到,ASCII 无法编码中文字符。str.encode() 方法返回的是一个字节序列——实际上是一个 bytes 对象。

通过以上的例子,也可以看到,编码后,显示的为 b'',这个字母 b 表示的是字节字面值,而非字符串字面值。在创建字节字面值时,我们可以混合打印可打印的 ASCII 字符与十六进制转义字符。比如字符 a 通过 utf8 编码后,编码的内容为 01000001,但是这个值显示起来并不便利,那么 01000001 这个值在 ASCII 表中为 a,因此

'a'.encode('utf8') # 输出为b'a'

但是,对于非 ASCII 码的字符,显然不能直接转换为 ASCII 码,那么怎么办呢?

"我".encode('UTF8') # 输出为b'\xe6\x88\x91'

这里就使用了\x 十六进制的方式等进行了替代了这些非 ASCII 字符,\x 表示 16 进制,显然 “我” 使用了 3 个字节表示。

接口测试中的实例

import requests

rsp = requests.get('https://www.baidu.com/').content
print(rsp) # 二进制
print('='*20)
print(rsp.decode('utf8')) # 二进制通过utf8解码为unicode,展示为字符形式

这里返回的是二进制格式的内容,也就是我们上面的说的 b''格式,不能通过 ASCII 字符展示的,就是通过十六进制的转义字符展示:\x。建议在所有的开发中均采用 UTF8 编码和解码保持一致性。

2 进制、8 进制、10 进制、16 进制的相互转换

print(ord('我')) # 返回的unicode10进制数值25105
print(bin(25105)) # 0b110001000010001 ,10进制转2进制
print(hex(0b110001000010001)) # 0x6211 ,2进制转16进制
# 这里也能知道“我”在unicode中编码的16进制的展示、2进制的展示

25105
0b110001000010001
0x6211

# 在unicode转码规则中,介绍的'我'在unicode编码是1100010 00010001,在uft8编码是:11100110 10001000 10010001
# 那我们看看呢
print(hex(0b111001101000100010010001)) # 0xe68891 ,2进制转16进制
print(bytes().fromhex('0xe68891'[2:]))
print('我'.encode('utf8')) #b'\xe6\x88\x91'

0xe68891
b'\xe6\x88\x91'
b'\xe6\x88\x91'

byte 与 str

byte 在 python 显示的如 b'a',b'\xe6\x88\x91',str 在 python 显示的是'a','我',那么字节和字符的长度是不一样的。如 len() 函数

len(b'\xe6\x88\x91') # 3,返回的字节长度
len(r'\xe6\x88\x91') # 12,返回的字符的长度

这也是为什么有时候在测试中,输入框的长度展示为 10,但是有时候输入中文的时候,字符长度实际<10 的时候,可能就是由于转成了字节长度计算的。

进制之间的相互转换:

hex(16) # 10进制整数转16进制字符串'0x10'
int(0xff) # 16进制字符串转为10进制整数255

bin(2) # 10进制整数转2进制字符串 '0b10'
int(0b10)  # 2进制字符串转10进制整数2

oct(9) # 10进制整数转8进制字符串'0o11'
int(0o11) # 8进制整数转10进制整数为9

# 当然,进制间也可以相互转换
hex(0o11) # '0x9'
bin(0x9) # '0b1001'
oct(0x9) # '0o377'

从上面可以看到在表示二进制的时候使用是 0b 、8 进制 0o、16 进制 0x,转换为 2、8、16 后,返回的均是字符串。

字符串转字节串

# 比如上面转成了16进制的字符串,字符串转为字节串
bytes().fromhex('0xe68891'[2:]) # b'\xe6\x88\x91'

# 来,我们看个例子
bytes([1]) # 返回b'\x01'
bytes([ord(1)]) # 返回的是b'1'

刚才这里例子说明的是,bytes 直接转的其实是转的 ascii 十进制的整数,为什么,bytes([1]) 返回的是 b'\x01',因为这个在 ascii 表中没没有对应的字面值,而 bytes[49] 有对应的字面值为 b'1'。

2.3 字符串,我来了

来来来,我们看下面:

print('\table')  #' able',有个空隙,这是为什么呢,这是由于\t在表示制表符。
print('\\table')  # '\table' ,可以通过\把\转义了,这样就能展示了
print(r'\table')  # '\table' ,可以通过r表示该字符串属于可读的,字符串中无需转义

关于字符串转义
image

有时候字符串过长,怎么换行呢?一下两种方式均可以换行。

t = "This" + \
" is a tip!"
t1 = ("This" 
     " is a tip!")
print(t1)

This is a tip!

Python3 格式化输出

  1. 通过% 输出:这种方式不建议,这主要是为了兼容 Python2 到 Python3

    "My name is %s" %("Alfred.Xue") # 字符串%s
    "I am %d years old." %(25)  # %d 整数
    "His height is %f m"%(1.70)  # %f 浮点数
    "His height is %.2f m"%(1.7033)  # %.2f指定保留2位#小数
    
  2. 通过 str.format{}进行字符串格式化,如下:

    "{0} is my {1}".format("张三",'name')
    "{{{0}}} 这里有几个花括号".format("哈哈哈哈,")  # 注意,如果需要带{}的话,那么需要写2次,可以显示一次。
    "{name},这是我的{0}".format("名字",name="ZS")  # 关键字参数在前,位置参数在后
    "{0[1]} ".format([1,2,3])
    "{} {}".format(1,2)
    

    有时候,我们会看到对字符串的格式化时,会带一些指定符号,如 s——表示强制使用字符串形式;a——强制使用其表象形式,仅限 ASCII 码;r——强制使用表象形式。如下:

"{0} {0!s} {0!a} {0!r}".format(decimal.Decimal(1.23))
# 1.23 1.23 Decimal(1.23) Decimal(1.23) 

除此以外,我们的格式也得排版下吧:

{0:20} 默认左对齐,最小占位 20

{0:<20} 左对齐,最小占位 20
{0:>20} 右对齐,最小占位 20

{0:20} 居中,最小占位 20

{0:-20} 居中,最小占位 20,字符串实际长度不够 20 的时候,使用 - 占位

{0:.>20} 右对齐,最小占位 20,字符串实际长度不够 20 的时候,使用.占位

{0:.2} 默认左对齐,最大占位 20,比如长度超过 2 的时候,字符串会被截断,只保留前 2 个字符

{0:0=20} 最小占位 20,且参数只能为数字,比如返回'-000000001234'

{0:b} 转为 2 进制

{0:o} 转为 8 进制

{0:x} 转为 16 进制小写
{0:X} 转为 16 进制大写

对了,还有一个神奇的 locals(),如下:

a = 10
b = 9
"{a} != {b}".format(**locals())  # 10 != 9
t = "你好" + \
    "啊"

t1 = ("你好"
      "那?")

print(t)
print(t1)

m = "12"
m1 = m * 3
print(m1)

s = "我的名字"
print(s.index('名字'))  # 返回索引位置,没有找到包ValueError
print(s.find('名字'))

s1 = "/usr/local/bin/firefox"
print(s1.rpartition('/'))  # ('/usr/local/bin', '/', 'firefox')

s2 = "abDC"
print(s2.startswith("a"))

s3 = "1982-01-19"
print(s3.split('-'))  # ['1982', '01', '19']

table = "{1}好吗?{1}".format("你", "张三")
print(table)

table = "{name}:{age}:{sex}".format(name="张三", age='18', sex='男')
print(table)

l = ['你', '我', '他']
table = '{0[2]}'.format(l)
print(table)

a = "aaa"
b = "bbb"
table = "{a} is {b}".format(**locals())
print(table)

table = '我的名字长度是'
table1 = "左对齐:{0:20}这么多".format(table)  # 左对齐
table2 = "右对齐:{0:>20}这么多".format(table)  # 右对齐
table3 = "居中:{0:^20}这么多".format(table)  # 居中
table4 = "居中,空白地方用-填充:{0:-^20}这么多".format(table)  # 居中,空白地方用-填充
table5 = "左对齐,空白地方用.填充: {0:.<20}这么多".format(table)  # 左对齐,空白地方用.填充
table6 = "大宽度为2,如果有空白地方就用.填充,否则table字符串需要裁剪:{0:.2}这么多".format(table)  # 最大宽度为2,如果有空白地方就用.填充,否则table字符串需要裁剪
for i in range(1,7):
    name = "table{i}".format(i=i)
    print(eval(name))


# 格式化数字
s = 123
s1 = '{0:d=12}'.format(s)
s2 = '{0:012}'.format(-134)
s3 = '{num:b} {num:o} {num:x} {num:X}'.format(num=132) # 2进制、8进制、16进制小写、16进制大写,不带前缀输出
s4 = '{num:#b} {num:#o} {num:#x} {num:#X}'.format(num=132) # 2进制、8进制、16进制小写、16进制大写,带前缀输出

print(s1)
print(s2)
print(s3)
print(s4)

你好啊
你好那?
121212
2
2
('/usr/local/bin', '/', 'firefox')
True
['1982', '01', '19']
张三好吗?张三
张三:18:男

aaa is bbb
左对齐:我的名字长度是 这么多
右对齐: 我的名字长度是这么多
居中: 我的名字长度是 这么多
居中,空白地方用 - 填充:------我的名字长度是 ------- 这么多
左对齐,空白地方用.填充: 我的名字长度是.............这么多
大宽度为 2,如果有空白地方就用.填充,否则 table 字符串需要裁剪:我的这么多
ddddddddd123
-00000000134
10000100 204 84 84
0b10000100 0o204 0x84 0X84

Python2 编码与 Python3 编码:

1、Python2 默认编码是 ascii 码,那么如果是这样的话,在显示非 ascii 码的字符的时候会比较让人搞不清楚。比如

name = "张三"
print name # 这里会报错

一般当时会在行首写一句 注释,表示使用 utf8 编码,比如:

# -*- coding:utf-8 -*-

# 定义源代码的编码
name = "张三"
print name # 这里不会报错

那么表示的就是采用 utf8 编码,

# -*- coding:utf-8 -*-
name = "张三"
name == u'张三' # 这里显然会报False,因为name为utf8编码 \xe5\xbc\xa0\xe4\xb8\x89,u'张三'为unicode编码\\u5f20\\u4e09

看到显然 Python2 的编码会比较麻烦。在 Python2 中有 2 种字符串类型:str 类型和 unicode 类型

2、在 Python3 中是将字节字符串与普通字符串严格区分的,所有的字符串都是'张三 abc'这种方式表示,字节串均使用 b''表示。文本编码总是 Unicode。


共收到 2 条回复 时间 点赞
MrCharp 关闭了讨论 04月16日 10:52
MrCharp 重新开启了讨论 04月16日 10:52

欢迎各位批评指导

深入浅出,详细阐述了字符和编码~👍

MrCharp 关闭了讨论 11月02日 23:03
MrCharp 重新开启了讨论 11月02日 23:03
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册