8.Python基础语法---04字符编码与字符串格式化

字符编码

字符串是一种数据类型,但字符串比较特殊的是还有一个编码问题。

乱码起因

计算机只能处理数字,如果要处理文本,就必须先把文本转换为数字才能处理。
计算机在设计时采用8个比特(bit)作为一个字节(byte)所以:一个字节能表示的最大的整数就是255(二进制11111111=十进制255;28 -1)。
要表示更大的整数,就必须用更多的字节。比如两个字节可以表示的最大整数是65535(216 - 1),4个字节可以表示的最大整数是4294967295(232 -1)

老美在发明计算机的时候,只有128个字符被编码到计算机里,也就是大小写英文字母、数字和一些符号,这个编码表被称为ASCII编码,比如大写字母S的编码是83,小写字母s的编码是115。ASCII编码对照表

ASCII ((American Standard Code for Information Interchange): 美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言。它是最通用的信息交换标准,并等同于国际标准ISO/IEC 646。ASCII第一次以规范标准的类型发表是在1967年,最后一次更新则是在1986年,到目前为止共定义了128个字符

ASCII一共才128个字符,用一个字节就可以足够编码(一个字节有256中字符可以编码)。

处理中文显然一个字节是不够的,至少需要两个字节,而且还不能和ASCII编码冲突,所以,中国制定了GB2312编码,用来把中文编进去。

GB2312编码:1981年5月1日发布的简体中文汉字编码国家标准。GB2312对汉字采用双字节编码,收录7445个图形字符,其中包括6763个汉字。

全世界有上百种语言,日本把日文编到Shift_JIS里,韩国把韩文编到Euc-kr里,各国有各国的标准,就会不可避免地出现冲突,结果就是,在多语言混合的文本中,显示出来会有乱码。
因此,Unicode应运而生。Unicode把所有语言都统一到一套编码里,这样就不会再有乱码问题了。

Unicode编码

Unicode标准也在不断发展,最常用的是用两个字节表示一个字符(如果要用到非常偏僻的字符,就需要4个字节)。现代操作系统和大多数编程语言都直接支持Unicode。
ASCII编码和Unicode编码的区别:ASCII编码是1个字节,而Unicode编码通常是2个字节。
字母A用ASCII编码是十进制的65,二进制的01000001;
字符0用ASCII编码是十进制的48,二进制的00110000,注意字符'0'和整数0是不同的;
汉字已经超出了ASCII编码的范围,用Unicode编码是十进制的20013,二进制的01001110 00101101。

你可以猜测,如果把ASCII编码的A用Unicode编码,只需要在前面补0就可以,因此,A的Unicode编码是00000000 01000001。
问题来了:如果统一成Unicode编码,乱码问题从此消失了。
但是,如果文本基本上全部是英文的话,用Unicode编码比ASCII编码需要多一倍的存储空间,在存储和传输上就十分不划算。本着节约的精神,又出现了把Unicode编码转化为“可变长编码”的UTF-8编码。

UTF-8编码

UTF-8 就是在互联网上使用最广的一种 Unicode 的实现方式。其他实现方式还包括 UTF-16(字符用两个字节或四个字节表示)和 UTF-32(字符用四个字节表示),不过在互联网上基本不用。
重复一遍,这里的关系是,UTF-8 是 Unicode 的实现方式之一。

UTF-8 最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。常用的英文字母被编码成1个字节,汉字通常是3个字节,只有很生僻的字符才会被编码成4个字节。如果你要传输的文本包含大量英文字符,用UTF-8编码就能节省空间:

UTF-8 的编码规则很简单,只有二条:

  1. 对于单字节的符号,字节的第一位设为0,后面7位为这个符号的 Unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的。
  2. 对于n字节的符号(n > 1),第一个字节的前n位都设为1,第n + 1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的 Unicode 码。

下表总结了编码规则,字母x表示可用编码的位。

Unicode符号范围(十六进制) UTF-8编码方式 (二进制)
0000 0000 ~ 0000 007F 0xxxxxxx
0000 0080 ~ 0000 07FF 110xxxxx 10xxxxxx
0000 0800 ~ 0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx
0001 0000 ~ 0010 FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

跟据上表,解读 UTF-8 编码非常简单。如果一个字节的第一位是0,则这个字节单独就是一个字符;如果第一位是1,则连续有多少个1,就表示当前字符占用多少个字节。

下面,还是以汉字为例,演示如何实现 UTF-8 编码。

的 Unicode 是6D6A(110110101101010),根据上表,可以发现6D6A处在第三行的范围内(0000 0800 - 0000 FFFF),因此的 UTF-8 编码需要三个字节,即格式是1110xxxx 10xxxxxx 10xxxxxx。然后,从的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。这样就得到了,的 UTF-8 编码是11100110 10110101 10101010,转换成十六进制就是E6B5AA

字符 ASCII Unicode UTF-8
A 01000001 00000000 01000001 01000001
无法表示 01101101 01101010 11100110 10110101 10101010

UTF-8编码有一个额外的好处,就是ASCII编码实际上可以被看成是UTF-8编码的一部分,所以,大量只支持ASCII编码的历史遗留软件可以在UTF-8编码下继续工作。

扩展

计算机系统通用的字符编码工作方式:
在计算机内存中,统一使用Unicode编码,当需要保存到硬盘或者需要传输的时候,就转换为UTF-8编码。
举个例子
用记事本编辑的时候,从文件读取的UTF-8字符被转换为Unicode字符到内存里,编辑完成后,保存的时候再把Unicode转换为UTF-8保存到文件:


image.png

浏览网页的时候,服务器会把动态生成的Unicode内容转换为UTF-8再传输到浏览器:

image.png

很多网页的源码上会有类似<meta charset="UTF-8" />的信息,表示该网页正是用的UTF-8编码。

Python字符串编码

Python 3版本中,字符串是以Unicode编码的,也就是说,Python的字符串支持多语言:

print('浪子大侠,你好!')
浪子大侠,你好

对于单个字符的编码,Python提供了ord()函数获取字符的整数表示,chr()函数把编码转换为对应的字符:

>>> ord('A')
65
>>> ord('浪')
28010
>>> chr(66)
'B'
>>> chr(20384)
'侠'

如果知道字符的整数编码,还可以用十六进制这么写str:

>>> ord('浪')
28010
>>> hex(28010)
'0x6d6a'
>>> ord('子')
23376
>>> hex(23376)
'0x5b50'
>>> ord('大')
22823
>>> hex(22823)
'0x5927'
>>> ord('侠')
20384
>>> hex(20384)
'0x4fa0'
>>> '\u6d6a\u5b50\u5927\u4fa0'
'浪子大侠'

由于Python的字符串类型是str,在内存中以Unicode表示,一个字符对应若干个字节。如果要在网络上传输,或者保存到磁盘上,就需要把str变为以字节为单位的bytes。

Python对bytes类型的数据用带b前缀的单引号或双引号表示:

x = b'ABC'

要注意区分'ABC'和b'ABC',前者是str,后者虽然内容显示得和前者一样,但bytes的每个字符都只占用一个字节。
以Unicode表示的str通过encode()方法可以编码为指定的bytes,例如:

>>> 'ABC'.encode('ascii')
b'ABC'
>>> '浪子大侠'.encode('utf-8')
b'\xe6\xb5\xaa\xe5\xad\x90\xe5\xa4\xa7\xe4\xbe\xa0'
>>> '浪子大侠'.encode('ascii')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-3: ordinal not in range(128)

纯英文的str可以用ASCII编码为bytes,内容是一样的,含有中文的str可以用UTF-8编码为bytes。含有中文的str无法用ASCII编码,因为中文编码的范围超过了ASCII编码的范围,Python会报错
在bytes中,无法显示为ASCII字符的字节,用\x##显示(二进制的十六进制表示)。

\x## 怎么来的呢
根据UTF-8 的编码规则确认二进制,再转十六进制,就明白了!

>>> ord('浪')
28010
>>> hex(28010)
'0x6d6a'
>>>bin(0x6d6a)
'0b110110101101010'

1110xxxx 10xxxxxx 10xxxxxx
'0b 110 110101 101010'
11100110 10110101 10101010

>>> ord('子')
23376
>>> hex(23376)
'0x5b50'
>>>bin(0x5b50)
'0b101101101010000'

1110xxxx 10xxxxxx 10xxxxxx
'0b 101 101101 010000'
11100101 10101101 10010000

>>> ord('大')
22823
>>> hex(22823)
'0x5927'
>>>bin(0x5927)
'0b101100100100111'

1110xxxx 10xxxxxx 10xxxxxx
'0b101 100100 100111'
11100101 10100100 10100111

>>> ord('侠')
20384
>>> hex(20384)
'0x4fa0'
>>>bin(0x4fa0)
'0b100111110100000'

1110xxxx 10xxxxxx 10xxxxxx
'0b100 111110 100000'
11100100 10111110 10100000

>>>hex(0b111001101011010110101010111001011010110110010000111001011010010010100111111001001011111010100000)
'0xe6b5aae5ad90e5a4a7e4bea0'

'0xe6 b5 aa e5 ad 90 e5 a4 a7 e4 be a0'
b'\xe6\xb5\xaa\xe5\xad\x90\xe5\xa4\xa7\xe4\xbe\xa0'

反过来,如果我们从网络或磁盘上读取了字节流,那么读到的数据就是bytes。要把bytes变为str,就需要用decode()方法:

>>> b'ABC'.decode('ascii')
'ABC'
>>> b'\xe6\xb5\xaa\xe5\xad\x90\xe5\xa4\xa7\xe4\xbe\xa0'.decode('utf-8')
'浪子大侠'

如果bytes中包含无法解码的字节,decode()方法会报错:

>>> b'\xe6\xb5\xaa\xe5\xad\x90\xe5\xa4\xa7\xe4\xbe\xff'.decode('utf-8')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'utf-8' codec can't decode bytes in position 9-10: invalid continuation byte

如果bytes中只有一小部分无效的字节,可以传入errors='ignore'忽略错误的字节:

>>> b'\xe6\xb5\xaa\xe5\xad\x90\xe5\xa4\xa7\xe4\xbe\xff'.decode('utf-8',errors='ignore')
'浪子大'

要计算str包含多少个字符,可以用len()函数:

>>> len('ABC')
3
>>> len('浪子大侠')
4

len()函数计算的是str的字符数,如果换成bytes,len()函数就计算字节数

>>> len(b'ABC')
3
>>> len(b'\xe6\xb5\xaa\xe5\xad\x90\xe5\xa4\xa7\xe4\xbe\xa0')
12
>>> len('浪子大侠'.encode('utf-8'))
12

可见,1个中文字符经过UTF-8编码后通常会占用3个字节,而1个英文字符只占用1个字节。

在操作字符串时,我们经常遇到str和bytes的互相转换。为了避免乱码问题,应当始终坚持使用UTF-8编码对str和bytes进行转换。

由于Python源代码也是一个文本文件,所以,当你的源代码中包含中文的时候,在保存源代码时,就需要务必指定保存为UTF-8编码。当Python解释器读取源代码时,为了让它按UTF-8编码读取,我们通常在文件开头写上这两行:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

第一行注释是为了告诉Linux/OS X系统,这是一个Python可执行程序,Windows系统会忽略这个注释;
第二行注释是为了告诉Python解释器,按照UTF-8编码读取源代码,否则,你在源代码中写的中文输出可能会有乱码。
申明了UTF-8编码并不意味着你的.py文件就是UTF-8编码的,必须并且要确保文本编辑器正在使用UTF-8 without BOM编码:

Python字符串格式化

格式化字符串字面值
内嵌表达式的字符串字面值。
格式字符串语法
使用 str.format() 进行字符串格式化。
printf 风格的字符串格式化
这里详述了使用 % 运算符进行字符串格式化。

格式化字符串字面值

格式化字符串字面值 或称 f-string 是带有 'f' 或 'F' 前缀的字符串字面值。这种字符串可包含替换字段,即以 {} 标示的表达式。而其他字符串字面值总是一个常量,格式化字符串字面值实际上是会在运行时被求值的表达式。

字符串在花括号以外的部分按其字面值处理,除了双重花括号 '{{' 或 '}}' 会被替换为相应的单个花括号。单个左花括号 '{' 标示一个替换字段,它以一个 Python 表达式打头,表达式之后可能有一个以叹号 '!' 标示的转换字段。之后还可能带有一个以冒号 ':' 标示的格式说明符。替换字段以一个右花括号 '}' 作为结束。

格式化字符串字面值中的表达式会被当作包含在圆括号中的普通 Python 表达式一样处理,但有少数例外。 空表达式不被允许,lambda 和赋值表达式 := 必须显式地加上圆括号。 替换表达式可以包含换行(例如在三重引号字符串中),但是不能包含注释。 每个表达式会在格式化字符串字面值所包含的位置按照从左至右的顺序被求值。

如果指定了转换符,表达式的求值结果会先转换再格式化。
转换符 '!s' 即对结果调用 str()
'!r' 为调用 repr()
'!a' 为调用 ascii()

在此之后结果会使用 format() 协议进行格式化。格式说明符会被传入表达式或转换结果的 __format__() 方法。如果省略格式说明符则会传入一个空字符串。然后格式化结果会包含在整个字符串最终的值当中。

顶层的格式说明符可以包含有嵌套的替换字段。这些嵌套字段也可以包含有自己的转换字段和 格式说明符,但不可再包含更深层嵌套的替换字段。这里的 格式说明符微型语言 与字符串 .format() 方法所使用的相同。

格式化字符串字面值可以拼接,但是一个替换字段不能拆分到多个字面值。

示例:

name = "Denny"
print(f"He said his name is {name!r}")
print(f"He said his name is {repr(name)}")
执行结果:
He said his name is 'Denny'
He said his name is 'Denny'

import decimal
width = 10
precision = 4
value = decimal.Decimal("12.34567")
print(f"result: {value:{width}.{precision}}") # ?width的含义width 是一个定义最小总字段宽度的十进制整数,包括任何前缀、分隔符和其他格式化字符。 如果未指定,则字段宽度将由内容确定。
执行结果:
result:      12.35

from datetime import datetime
today = datetime(year=2020, month=3, day=14)
print(f"{today:%B %d, %Y}")
执行结果:
March 14, 2020

number = 1024
print(f"{number:#0x}")
print(f"{number:#0o}")
print(f"{number:#0b}")
执行结果:
0x400
0o2000
0b10000000000

与正常字符串字面值采用相同语法导致的一个结果就是替换字段中的字符不能与外部的格式化字符串字面值所用的引号相冲突:
内外需要使用不同的引号

age = {
    'Denny':23,
    'Sunny':24,
    'Paul':25
}
# print(f"Denny age is {age["Denny"]}") # SyntaxError: invalid syntax
print(f"Denny age is {age['Denny']}")
print(f'Sunny age is {age["Sunny"]}')
执行结果:
Denny age is 23
Sunny age is 24

格式表达式中不允许有反斜杠,这会引发错误:

print(f"newline: {ord('\n')}")
执行结果:
SyntaxError: f-string expression part cannot include a backslash

newline = ord('\n')
print(f"newsline: {newline}")
执行结果:
newsline: 10

格式化字符串字面值不可用作文档字符串,即便其中没有包含表达式。

>>> def test():
...     '''test ok'''
... 
>>> test.__doc__
'test ok

>>> def foo():
...     f"Not a docstring"
...
>>> foo.__doc__ is None
True

格式字符串语法

str.format() 方法和 Formatter 类共享相同的格式字符串语法(虽然对于 Formatter 来说,其子类可以定义它们自己的格式字符串语法)。 具体语法与格式化字符串字面值相似,但也存在区别。

格式字符串包含有以花括号 {} 括起来的“替换字段”。 不在花括号之内的内容被视为字面文本,会不加修改地复制到输出中。 如果你需要在字面文本中包含花括号字符,可以通过重复来转义: {{ and }}。

用不太正式的术语来描述,替换字段开头可以用一个 field_name 指定要对值进行格式化并取代替换字符被插入到输出结果的对象。 field_name 之后有可选的 conversion 字段,它是一个感叹号 '!' 。加一个 format_spec,并以一个冒号 ':' 打头。 这些指明了替换值的非默认格式。

field_name 本身以一个数字或关键字 arg_name 打头。 如果为数字,则它指向一个位置参数,而如果为关键字,则它指向一个命名关键字参数。 如果格式字符串中的数字 arg_names 为 0, 1, 2, ... 的序列,它们可以全部省略(而非部分省略),数字 0, 1, 2, ... 将会按顺序自动插入。 由于 arg_name 不使用引号分隔,因此无法在格式字符串中指定任意的字典键 (例如字符串 '10' 或 ':-]') arg_name 之后可以带上任意数量的索引或属性表达式。 '.name' 形式的表达式会使用 getattr() 选择命名属性,而 '[index]' 形式的表达式会使用 __getitem__() 执行索引查找。
一些简单的格式字符串示例

"First, thou shalt count to {0}"  # References first positional argument
"Bring me a {}"                   # Implicitly references the first positional argument
"From {} to {}"                   # Same as "From {0} to {1}"
"My quest is {name}"              # References keyword argument 'name'
"Weight in tons {0.weight}"       # 'weight' attribute of first positional arg
"Units destroyed: {players[0]}"   # First element of keyword argument 'players'.

使用 conversion 字段在格式化之前进行类型强制转换。 通常,格式化值的工作由值本身的 __format__() 方法来完成。 但是,在某些情况下最好强制将类型格式化为一个字符串,覆盖其本身的格式化定义。 通过在调用 __format__() 之前将值转换为字符串,可以绕过正常的格式化逻辑。

目前支持的转换旗标有三种: '!s' 会对值调用 str()'!r' 调用 repr()'!a' 则调用 ascii()

"Harold's a clever {0!s}"        # Calls str() on the argument first
"Bring out the holy {name!r}"    # Calls repr() on the argument first
"More {!a}"                      # Calls ascii() on the argument first

format_spec 字段包含值应如何呈现的规格描述,例如字段宽度、对齐、填充、小数精度等细节信息。 每种值类型可以定义自己的“格式化迷你语言”或对 format_spec 的解读方式。

大多数内置类型都支持同样的格式化迷你语言

format_spec 字段还可以在其内部包含嵌套的替换字段。 这些嵌套的替换字段可能包括字段名称、转换旗标和格式规格描述,但是不再允许更深层的嵌套。 format_spec 内部的替换字段会在解读 format_spec 字符串之前先被解读。 这将允许动态地指定特定值的格式。

格式规格迷你语言

格式示例

本节包含 str.format() 语法的示例以及与旧式 % 格式化的比较。

该语法在大多数情况下与旧式的 % 格式化类似,只是增加了 {}: 来取代 %。 例如,,'%03.2f' 可以被改写为 '{:03.2f}'

新的格式语法还支持新增的不同选项,将在以下示例中说明。

  1. 按位置访问参数:
>>> '{0}, {1}, {2}'.format('a', 'b', 'c')
'a, b, c'
>>> '{}, {}, {}'.format('a', 'b', 'c')  # 3.1+ only
'a, b, c'
>>> '{2}, {1}, {0}'.format('a', 'b', 'c')
'c, b, a'
>>> '{2}, {1}, {0}'.format(*'abc')      # 解包字符串系列
'c, b, a'
>>> '{0}{1}{0}'.format('abra', 'cad')   # 参数的索引可以重复
'abracadabra'
  1. 按名称访问参数:
>>> 'Sunny: {sex}, {age}'.format(sex='男', age=23)
Sunny: 男, 23
>>> sunny = {'sex': '男', 'age': 23}
>>> 'Sunny: {sex}, {age}'.format(**sunny)
Sunny: 男, 23
  1. 访问参数的属性:
c = 3-5j
print('The complex number {0} is formed from the real part {0.real}\
 and the imaginary part {0.imag}.'.format(c))
执行结果:
The complex number (3-5j) is formed from the real part 3.0 and the imaginary part -5.0.
class Point:
    def __init__(self, x, y):
        self.x, self.y = x, y
    def __str__(self):
        return 'Point({self.x}, {self.y})'.format(self=self)

print(Point(4, 2))
执行结果:
Point(4, 2)
  1. 访问参数的项:
coord = (3,5)
print('X:{0[0]}; Y:{0[1]}'.format(coord))
执行结果:
X:3; Y:5
  1. 替代 %s 和 %r:
>>> "repr() shows quotes: {!r}; str() doesn't: {!s}".format('test1', 'test2')
"repr() shows quotes: 'test1'; str() doesn't: test2"
  1. 对齐文本以及指定宽度:
>>> '{:<30}'.format('left aligned')
'left aligned                  '
>>> '{:>30}'.format('right aligned')
'                 right aligned'
>>> '{:^30}'.format('centered')
'           centered           '
>>> '{:*^30}'.format('centered')  # use '*' as a fill char
'***********centered***********'
  1. 替代 %+f, %-f 和 % f 以及指定正负号:
>>> '{:+f}; {:+f}'.format(3.14, -3.14)  # 总是显示符号位
'+3.140000; -3.140000'
>>> '{: f}; {: f}'.format(3.14, -3.14)  # 正数前显示空格
' 3.140000; -3.140000'
>>> '{:-f}; {:-f}'.format(3.14, -3.14)  # 只显示负数的符号 同 '{:f}; {:f}'
'3.140000; -3.140000'
  1. 替代 %x 和 %o 以及转换基于不同进位制的值:
>>> # format also supports binary numbers
>>> "int: {0:d};  hex: {0:x};  oct: {0:o};  bin: {0:b}".format(42)
'int: 42;  hex: 2a;  oct: 52;  bin: 101010'
>>> # with 0x, 0o, or 0b as prefix:
>>> "int: {0:d};  hex: {0:#x};  oct: {0:#o};  bin: {0:#b}".format(42)
'int: 42;  hex: 0x2a;  oct: 0o52;  bin: 0b101010'
  1. 使用逗号作为千位分隔符:
>>> '{:,}'.format(1234567890)
'1,234,567,890'
  1. 表示为百分数:
>>> points = 19
>>> total = 22
>>> 'Correct answers: {:.2%}'.format(points/total)
'Correct answers: 86.36%'
  1. 使用特定类型的专属格式化:
>>> import datetime
>>> d = datetime.datetime(2020, 3,14, 2, 11, 58)
>>> '{:%Y-%m-%d %H:%M:%S}'.format(d)
'2020-03-14 02:11:58'
  1. 嵌套参数以及更复杂的示例:
>>> for align, text in zip('<^>', ['left', 'center', 'right']):
...     '{0:{fill}{align}16}'.format(text, fill=align, align=align)
...
'left<<<<<<<<<<<<'
'^^^^^center^^^^^'
'>>>>>>>>>>>right'
>>>
>>> octets = [192, 168, 0, 1]
>>> '{:02X}{:02X}{:02X}{:02X}'.format(*octets)
'C0A80001'
>>> int(_, 16) # 在python交互模式下,_符号是指交互解释器中最后一次执行语句的返回结果。发现print无效
3232235521
>>>
>>> width = 5
>>> for num in range(5,12): 
...     for base in 'dXob':
...         print('{0:{width}{base}}'.format(num, base=base, width=width), end=' ')
...     print()
...
    5     5     5   101
    6     6     6   110
    7     7     7   111
    8     8    10  1000
    9     9    11  1001
   10     A    12  1010
   11     B    13  1011

printf 风格的字符串格式化 (并不推荐使用)

此处介绍的格式化操作具有多种怪异特性,可能导致许多常见错误(例如无法正确显示元组和字典)。 使用较新的 格式化字符串字面值str.format() 接口或 模板字符串 有助于避免这样的错误。 这些替代方案中的每一种都更好地权衡并提供了简单、灵活以及可扩展性优势。

字符串具有一种特殊的内置操作:使用 % (取模) 运算符。 这也被称为字符串的 格式化 或 插值 运算符。 对于 format % values (其中 format 为一个字符串),在 format 中的 % 转换标记符将被替换为零个或多个 values 条目。 其效果类似于在 C 语言中使用 sprintf()。

如果 format 要求一个单独参数,则 values 可以为一个非元组对象。否则的话,values 必须或者是一个包含项数与格式字符串中指定的转换符项数相同的元组,或者是一个单独映射对象(例如字典)。

转换标记符包含两个或更多字符并具有以下组成,且必须遵循此处规定的顺序:

  1. '%' 字符,用于标记转换符的起始。
  2. 映射键(可选),由加圆括号的字符序列组成 (例如 (somename))。
  3. 转换旗标(可选),用于影响某些转换类型的结果。
  4. 最小字段宽度(可选)。 如果指定为 '*' (星号),则实际宽度会从 values 元组的下一元素中读取,要转换的对象则为最小字段宽度和可选的精度之后的元素。
  5. 精度(可选),以在 '.' (点号) 之后加精度值的形式给出。 如果指定为 '*' (星号),则实际精度会从 values 元组的下一元素中读取,要转换的对象则为精度之后的元素。
  6. 长度修饰符(可选)。
  7. 转换类型。

当右边的参数为一个字典(或其他映射类型)时,字符串中的格式必须包含加圆括号的映射键,对应 '%' 字符之后字典中的每一项。 映射键将从映射中选取要格式化的值。 例如:

>>>
print('%(language)s has %(number)03d quote types.' %
      {'language': "Python", "number": 2})
Python has 002 quote types.
# 在此情况下格式中不能出现 * 标记符(因其需要一个序列类的参数列表)。
标志 含义
'#' 值的转换将使用“替代形式”(具体定义见下文)。
'0' 转换将为数字值填充零字符。
'-' 转换值将靠左对齐(如果同时给出 '0' 转换,则会覆盖后者)。
' ' (空格) 符号位转换产生的正数(或空字符串)前将留出一个空格。
'+' 符号字符 ('+' 或 '-') 将显示于转换结果的开头(会覆盖 "空格" 旗标)。

可以给出长度修饰符 (h, l 或 L),但会被忽略,因为对 Python 来说没有必要 -- 所以 %ld 等价于 %d。

转换类型为:

转换符 含义 注释
'd' 有符号十进制整数。
'i' 有符号十进制整数。
'o' 有符号八进制数。 (1)
'u' 过时类型 -- 等价于 'd' (6)
'x' 有符号十六进制数(小写)。 (2)
'X' 有符号十六进制数(大写)。 (2)
'e' 浮点指数格式(小写)。 (3)
'E' 浮点指数格式(大写)。 (3)
'f' 浮点十进制格式。 (3)
'F' 浮点十进制格式。 (3)
'g' 浮点格式。 如果指数小于 -4 或不小于精度则使用小写指数格式,否则使用十进制格式。 (4)
'G' 浮点格式。 如果指数小于 -4 或不小于精度则使用大写指数格式,否则使用十进制格式。 (4)
'c' 单个字符(接受整数或单个字符的字符串)。
'r' 字符串(使用 repr() 转换任何 Python 对象)。 (5)
's' 字符串(使用 str() 转换任何 Python 对象)。 (5)
'a' 字符串(使用 ascii() 转换任何 Python 对象)。 (5)
'%' 不转换参数,在结果中输出一个 '%' 字符。

注释:

  1. 此替代形式会在第一个数码之前插入标示八进制数的前缀 ('0o')。
  2. 此替代形式会在第一个数码之前插入 '0x''0X' 前缀(取决于是使用 'x' 还是 'X' 格式)。
  3. 此替代形式总是会在结果中包含一个小数点,即使其后并没有数码。
    小数点后的数码位数由精度决定,默认为 6。
  4. 此替代形式总是会在结果中包含一个小数点,末尾各位的零不会如其他情况下那样被移除。
    小数点前后的有效数码位数由精度决定,默认为 6。
  5. 如果精度为 N,输出将截短为 N 个字符。
  6. 参见 PEP 237
    由于 Python 字符串显式指明长度,%s 转换不会将 '\0' 视为字符串的结束。

参考:
廖雪峰Python字符串和编码
阮一峰_字符编码笔记:ASCII,Unicode 和 UTF-8
Python官方文档

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容