4. 变量类型
本章介绍 Python 的内置变量类型。
我认为以下内置变量类型是在 Python 中经常用到、或者必须有所了解的:
类型 |
关键字 |
说明 |
例子 |
---|---|---|---|
【数字】 |
|||
整型 |
|
整数 |
|
浮点型 |
|
浮点数 |
|
复数型 |
|
复数 |
|
【序列】 |
|||
列表 |
|
一串有序的可变数据序列,每一项数据可以是任意类型。 |
|
元组 |
|
一串有序的不可变数据序列,在创建时就固定了每一项的数据值。 |
|
字符串 |
|
一串文本字符组成的不可变序列。 |
|
【映射】 |
|||
字典 |
|
一些互不相同的键及它们各自对应的值组成的键值对数据集。 |
|
【集合】 |
|||
集合 |
|
一些互不相同的数据值组成的无序可变数据集。 |
|
【其他】 |
|||
布尔型 |
|
表示真或假的逻辑类型。 |
|
空对象 |
|
表示空。 |
|
以上并不是 Python 的全部内置类型:
一些高级的、复杂的变量类型,例如
range
构造器,不再在这里列出。它们会在后续的章节进行介绍。一些较少使用到的类型,比如
byte
二进制字节类型,不会在本文的任何章节介绍。
4.1. 布尔型与空对象
在介绍其他的变量类型之前,先介绍这两个特殊的类型。
4.1.1. 布尔型
布尔型有真(True)或假(False)两种逻辑值,单词的首字母大写。常用的逻辑运算:
逻辑与:全真才为真
x and y
逻辑或:含真即为真
x or y
逻辑非:
not x
逻辑异或:相异为真
x ^ y
以上 x
与 y
均是布尔型变量。
[19]:
x = True
y = False
print(x and y, x or y, not x, x ^ y)
False True False True
4.1.2. 空对象
None 是 Python 中的空对象,它或许并不常用,但读者有必要了解。
空对象的逻辑值为假:
[20]:
x = None
print(bool(x))
False
4.2. 数字类型:int, float, complex
数字类型没有太多需要介绍的地方。
四则运算:
+
,-
,*
,/
整除与取余:
c = a // b
,d = a % b
;或者c, d = divmod(a,b)
。这里的整除是指向负无穷取整,例如
-5//2
的结果是-3
。复数不能参与整除或取余运算。
乘方:
a ** b
,或者pow(a, b)
取模:
abs(a)
。如果a
是复数,那么会计算模长;如果是整数或浮点数,实质上就是取绝对值。自运算:
a += 1
即a
自增 1;同理有-=
,*=
,/=
值得注意的点:
只要有除法参与的数学运算,其结果一定是浮点型。
只要有浮点型参与的数学运算,其结果也一定是浮点型。
Python 的内部机制已经处理了整数溢出的问题,因此无须担心。
虽然在数学上不合法,但是在 Python(以及一众编程语言)中,
0 ** 0
等于 1。
特别地,浮点型中还包含两个特殊的值,分别是”非数“(Not a Number, nan
)与”正/负无穷“(Infinity, inf
):
[21]:
x, y, z = 'nan', 'inf', '-inf'
print(float(x), float(y), float(z))
nan inf -inf
复数的使用非常少见,随便举个例子吧:
[22]:
x = complex(1, 5)
y = complex(2, -1)
z = x + y
print(z, abs(z))
(3+4j) 5.0
4.2.1. 类型转换与取整
Python 中从浮点型到整型的强制类型转换会截断小数点之后的部分:
[23]:
a, b, c, d = 1.2, 1.6, -1.2, -1.6
print(int(a), int(b), int(c), int(d))
1 1 -1 -1
要实现复杂的取整控制,可以调用 Python 内置的 math
模块:
floor:向负无穷取整。
ceil:向正无穷取整。
[24]:
import math # 导入 math 模块
print(math.floor(a), math.ceil(b), math.floor(c), math.ceil(d))
1 2 -2 -1
不过在我个人的实践中,取整与四舍五入进位的任务通常由 numpy
库代劳;有兴趣的读者,可以阅读 Numpy 的相关函数:
4.2.2. 比较数字大小
常规的数字大小判断:
小于
<
与小于等于<=
大于
>
与大于等于>=
等于
==
与不等于!=
[25]:
x = 3
y = 4
print(x != y)
True
特别地,Python 还支持“三元比较”:
[26]:
print(3 < 4 == 4, 3 > 2 < 4, 1 < 3 <= 5)
True True True
重要
不要试图用双等号比较两个浮点值的是否相等!
浮点计算是有精度的,直接比较它们是否相等是不明智的:
[27]:
x, y = 0.1, 0.2
z = 0.3
print(x+y, z, x+y==z)
0.30000000000000004 0.3 False
关于高精度的数学计算,推荐配合科学计算库 NumPy 使用。
4.3. 列表:list
Python 的三种常用序列 list, tuple, str, 我们先讲列表 list;列表大概是最接近其他编程语言的序列了。
列表序号从 0 开始。
Python 中的列表类似于其他语言的数组,不过列表的长度可变、并且元素不必是同一类型的。
虽然列表中可以包含不同类型的元素,但从编程习惯上说,个人不推荐这样做。
[28]:
x = [1, 2, 'a', 4]
y = [] # 空列表
print(x)
[1, 2, 'a', 4]
Python 中的列表默认支持常见所有的序列操作:
索引元素:单个选取
x[index]
,切片型选取x[start:end:step]
元素个数:
len(x)
追加:单个元素
x.append(item)
,追加一个列表x.extend(y)
插入:
x.insert(index, item)
排序:
按值排序:
x.sort()
,或者带返回值的sorted(x)
反序:
x.reverse()
,或者带返回值的reversed(x)
查询:
判断是否包含
item in x
判断包含的次数
x.count(item)
返回索引序数
x.index(item)
删除:
按索引:弹出并返回一个元素
x.pop(index)
,直接删除元素del(x[index])
按值:移除等于给定值的项
x.remove(item)
清空:
x.clear()
最值:最大值
max(x)
,最小值min(x)
上述叙述中, x
与 y
均表示列表, index
表示序数(整数类型), item
表示列表元素。
4.3.1. 索引元素
列表中最基础的操作就是根据索引序号,取出单个(或多个)元素:
[29]:
x = [1, 2, 3, 4, 5]
print(x[0], x[4])
1 5
Python 支持负数索引,比如 x[-1]
表示列表的倒数第 1 位元素:
[30]:
x = [1, 2, 3, 4, 5]
print(x[-2])
4
Python 支持一种切片语法,可以指定索引序号的起始、终止、步长,来选取多个元素。
x[start:end]
:从x[start]
依次选取到x[end-1]
x[start:end:step]
:从x[start]
每step
个元素选取依次,直到x[end-1]
(或它之前最后一个能被选取到的元素)。步长可以是负数,但相应地必须有 start >= endx[start:]
或者x[:end]
:从x[start]
选取到末尾,或者从起始选取到x[end-1]
。这其实是忽略了冒号一侧的项,也可以用空值 None 补位
重要
切片选取的结束是第 end-1 个元素,而不是第 end 个元素。
Python这样设计的原因是,这样保证了切片 x[start:end] 的长度恰好是 end 减去 start,而不是 end-start+1.
[31]:
# 选取 0 到 3,注意末尾被舍去
x = [1, 2, 3, 4, 5]
print(x[0:4])
[1, 2, 3, 4]
[32]:
# 选取 0 到 3 (或4),每 2 个选取一次
print(x[0:4:2], x[0:5:2])
[1, 3] [1, 3, 5]
[33]:
# 负数步长
print(x[::-1])
[5, 4, 3, 2, 1]
[34]:
# 从 1 选取到末尾,或从起始选取至倒数第 2 项
print(x[1:], x[:-1])
[2, 3, 4, 5] [1, 2, 3, 4]
[35]:
# 也可以忽略某一项,等同于 None
print(x[::2], x[None:None:2])
[1, 3, 5] [1, 3, 5]
赋值可以直接进行:
[36]:
x = [1, 2, 3, 4, 5]
x[:2] = [6, 7] # 本质是将右侧解包,然后分别传入两个元素位
print(x)
[6, 7, 3, 4, 5]
4.3.2. 元素个数
Python 的 len()
函数是一个独立的函数,并不是通过 x.len()
的方式调用的。这其中设计的差别,读者可以仔细体会。
该函数不止适用于列表,也适用于其他序列类型。
[37]:
x = [1, 2, 3, 4, 5]
print(len(x))
5
4.3.3. 追加
Python 的追加元素使用 x.append()
:
[38]:
x = [1, 2, 3, 4, 5]
x.append(-2)
print(x)
[1, 2, 3, 4, 5, -2]
要追加一个列表,使用 x.extend()
:
[39]:
x = [1, 2, 3, 4, 5]
x.extend([-2, -1])
print(x)
[1, 2, 3, 4, 5, -2, -1]
注意, x.append()
这种 Python 内置实例的方法并不返回任何值。所以,你不能把它赋值到另一个变量:
[40]:
y = [1, 2, 3, 4, 5].append(-2)
print(y) # 无效的赋值,因为它并没有返回值
None
要实现这种“返回值”效果,可以考虑下面两种方法之一:
加号:Python 支持用加号连接两个可变列表,从而把它们“融合”在一起
星号:Python 支持用前缀星号的方式进行列表展开
[41]:
x = [1, 2, 3]
y1 = x + [4, 5]
y2 = [*x, 4, 5]
print(y1, y2)
[1, 2, 3, 4, 5] [1, 2, 3, 4, 5]
4.3.4. 插入
用 x.insert(index, item)
将元素插入到第 index
个元素的位置,原第 index 及以后的元素依次后移:
[42]:
x = [1, 2, 3]
x.insert(1, -1) # 插入到 x[1] 处
print(x, x[1])
[1, -1, 2, 3] -1
要实现类似上一小节的“返回值”效果,仍然可以使用加号或者星号两种方式:
[43]:
x = [1, 2, 3]
item = -1
y1 = x[:1] + [item] + x[1:]
y2 = [*x[:1], item, *x[1:]]
print(y1, y2)
[1, -1, 2, 3] [1, -1, 2, 3]
4.3.5. 排序
4.3.5.1. 按值排序
用 x.sort()
对列表进行排序,默认是按升序。Python 的排序是稳定排序。
[44]:
x = [1, 4, 3, 2]
x.sort()
print(x)
[1, 2, 3, 4]
添加 reverse=True
选项,可以按降序进行排列。
[45]:
x = [1, 4, 3, 2]
x.sort(reverse=True)
print(x)
[4, 3, 2, 1]
如果列表内的元素不是数字,是列表或其他序列,会按 <
比较的方式来排序。下面是对列表元素进行排序:
[46]:
x = [[1, 2], [4, 3, 2], [3, 4], [3, 2]]
x.sort()
print(x)
[[1, 2], [3, 2], [3, 4], [4, 3, 2]]
要实现类似上一小节的“返回值”效果,仍然可以使用加号或者星号两种方式:
[47]:
x = [1, 4, 3, 2]
print(sorted(x, reverse=True))
[4, 3, 2, 1]
4.3.5.2. 反序
列表反序有三种方式:
就地反序:
x.reverse()
带返回值的反序:
利用
reversed
生成器:list(reversed(x))
利用步长为 -1 的切片:
x[::-1]
[48]:
x = [1, 4, 3, 2]
x_copy = [1, 4, 3, 2]
x.reverse()
y1 = list(reversed(x_copy))
y2 = x_copy[::-1]
print(x, x==y1, x==y2)
[2, 3, 4, 1] True True
4.3.6. 查询
要查询一个元素是否在列表中,使用 in
关键字:
[49]:
x = [1, 2, 3, 4]
print(2 in x, 5 in x)
True False
相反地,要确定一个元素是否不再列表中,使用 not in
来取非:
[50]:
x = [1, 2, 3, 4]
print(2 not in x, 5 not in x)
False True
要查询一个元素在列表中的位置,使用 x.index(item)
。
如果列表中该元素出现了多次,
x.index()
只会返回最靠前的那项对应的索引序数。如果要返回该元素在列表中出现的所有位置,可以使用列表解析(参考下方的列表解析一节)功能。
[51]:
x = [1, 2, 3, 2]
item = 2
y1 = x.index(item)
y2 = [i for i, v in enumerate(x) if v == item]
print(y1, y2, sep='\n')
1
[1, 3]
如果 item
不在列表中,这样会弹出 ValueError 错误,提示你要查询的对象并不在列表中:
[52]:
x.index(5)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-52-96ad5df81983> in <module>
----> 1 x.index(5)
ValueError: 5 is not in list
使用 x.count()
函数,可以避免该错误:
[53]:
x = [1, 2, 3, 2]
print(x.count(2), x.count(5))
2 0
4.3.7. 删除
4.3.7.1. 按索引删除
用 x.pop()
弹出列表末尾的元素,或用 x.pop(index)
弹出第 index 位的元素:
[54]:
x = [1, 2, 3, 4]
y = x.pop()
print(y, x)
4 [1, 2, 3]
[55]:
x = [1, 2, 3, 4]
y = x.pop(2)
print(y, x)
3 [1, 2, 4]
或者直接用 del(index)
命令来删除元素 x[index]
:
[56]:
x = [1, 2, 3, 4]
del(x[1])
print(x)
[1, 3, 4]
4.3.7.2. 按值删除
给定一个值 item
,用 x.remove(item)
可以删除列表中等于 item
的项:
类似于
x.index()
,它只会删除最靠前的匹配项。考虑使用列表解析(参考下方的列表解析一节)来移除所有的匹配项。
[57]:
x = [1, 2, 3, 2]
x_copy = [1, 2, 3, 2]
item = 2
x.remove(item)
y = [k for k in x_copy if k != item]
print(x, y, sep='\n')
[1, 3, 2]
[1, 3]
4.3.7.3. 清空列表
使用 x.clear()
来清空列表为 []
:
[58]:
x = [1, 2, 3, 4]
x.clear()
print(x)
[]
4.3.8. 最值
用 max()
或 min()
函数返回列表中的最值。类似于 len()
,这两个函数对其他的序列变量类型也同样有效。
[59]:
x = [1, 3, 4, 2]
print(min(x), max(x))
1 4
4.4. 高级列表操作
4.4.1. 列表展开
可以“就地”把列表拆开成多个以逗号分隔的元素。除了上文提到的展开为列表中的项(比如 [1, *x, 2]
),更常见的是拆成函数的传入参数。比如取余函数 divmod()
接受两个参数:
[60]:
x = [7, 2]
print(divmod(*x)) # 7÷2=3 … 1
(3, 1)
4.4.2. 列表解析
列表解析(也称列表推导)通过 for 循环与 if 判断来筛选列表中的元素。
[61]:
x = [1, 2, 3, 4, 5, 6]
y = [k+10 for k in x[:3]]
z = [k for k in x if k % 2 == 1]
print(y, z, sep='\n')
[11, 12, 13]
[1, 3, 5]
4.5. 元组
Python 中的元组与列表类似,但是元素不可变。
元组与列表有不同的使用场景。
元组的元素之间以逗号隔开,外侧的圆括号是可选的。
[62]:
x = 1, 2, 3 # 等同于 x = (1, 2, 3)
y = () # 空元组
z = (1,) # 单元素元组
元组也可以解包,将内部值赋给多个变量:
[63]:
a, b, c = x
print(a, b, c)
1 2 3
元组的索引与列表的方式一致,只是不能赋值给元组中的元素:
[64]:
x = 1, 2, 3, 4, 5
x[::2]
[64]:
(1, 3, 5)
元组的长度也可以用 len()
函数获取:
[65]:
x = 1, 2, 3
print(len(x))
3
4.6. 字符串
字符串是一种不可变序列。因此,字符串不存在“就地改动”,任何字符串方法都不会改变原字符串的值。
它在创建时可以用单引号包括,也可以用双引号包括。内部的引号可以用反斜线 \
转义:
[66]:
x = "It's a string."
y = 'It\'s a string.'
print(x, y, x == y)
It's a string. It's a string. True
字符串可以用加号与乘号来进行连接与重复;如果要连接的均是字符串面值(直接用引号括起的值,而不是字符串变量),可以用空格进行连接。
[67]:
x = "abc" * 3 + "def"
y = ("This is a single-line string "
"but I can write it across lines.")
z = x + 'qwe' # 字符串变量也可用加号与面值连接
print(x, y, z, sep='\n')
abcabcabcdef
This is a single-line string but I can write it across lines.
abcabcabcdefqwe
多行字符串可以用三个连续的单引号(或双引号)包括。多行字符串内容的所有换行都会被保留;可以在行尾添加一个反斜线 \
来忽略当前行的换行:
[68]:
x = """\
This is
a multiline
string.
"""
print(x)
This is
a multiline
string.
4.6.1. 强制类型转换
用 str()
可以强制将其他类型转换为字符串(如果能够转换):
[69]:
x = 123
y = True
z = [3, [1, 2], 4]
print(str(x), str(y), str(z), sep='\n')
123
True
[3, [1, 2], 4]
字符串也可以转换为列表,本质是按字符依次拆开:
[70]:
x = "abcdefg"
print(list(x))
['a', 'b', 'c', 'd', 'e', 'f', 'g']
4.6.2. 转义与 r 字符串
常见的转移字符包括:
字符 |
含义 |
---|---|
|
退格 |
|
换行 |
|
回车(移动到行首) |
|
制表符 |
|
被转义的反斜线 |
如果不想让反斜线进行转义,在字符串的 左侧引号之前 添加 r
实现:
[71]:
x = "String with\tTAB."
y = r"String with\tTAB."
print(x, y, sep='\n')
String with TAB.
String with\tTAB.
4.6.3. 字符串索引
字符串的索引仍然是字符串。Python 中没有字符类型,单个字符只是长度为 1 的字符串。
字符串的索引仍然支持
[72]:
x = "This is a string."
x[:6]
[72]:
'This i'
[73]:
x[::2]
[73]:
'Ti sasrn.'
唯一不同于列表索引的是,字符串在切片选取时,可以超出索引范围而不报错:
[74]:
x = "This is a string."
print(x[5:2333])
is a string.
4.6.4. 分割字符串
Python 中字符串分割函数主要有 split()
, rsplit()
与 splitline()
三个方法。
使用 x.split(sep, maxsplit)
来用 sep 字符串分割字符串 x
,并指定最多分割 maxsplit 次:
分割字符串长度可以大于 1
默认的
maxsplit=-1
,即会完全分割字符串
[75]:
x = "This is a string."
print(x.split('s')) # 默认完全分割
print(x.split('s', 1)) # 最多分割 1 次,即分割成 2 份
print(x.split('z')) # 空分割
print(x.split()) # 默认以空格分割
['Thi', ' i', ' a ', 'tring.']
['Thi', ' is a string.']
['This is a string.']
['This', 'is', 'a', 'string.']
我们经常把这个技巧用在 for 循环语句中:
[76]:
fruits = "apple,pear,orange,banana"
for fruit in fruits.split(','):
print(fruit)
apple
pear
orange
banana
Python 还提供了 rsplit()
,可以从字符串的右侧开始分割。从下例比较两者的区别:
[77]:
x = "This is a string."
print(x.split('s', 1))
print(x.rsplit('s', 1))
['Thi', ' is a string.']
['This is a ', 'tring.']
最后是 splitlines(keepends=False)
,它可以按行分隔符集中的所有符号进行分割(因此比手动地使用 split('\n')
的效果更好):
在
keepends=True
时,它能保留换行符。关于该函数认定的换行符号集,参考 官方文档:str.splitlines() 。
它与
split()
函数的另一个差别是对末尾空白行的处理,读者可以参考下例。
[78]:
x = "line 1\nline 2\r\nline 3\n"
print(x.splitlines())
print(x.splitlines(keepends=True))
print(x.split('\n')) # 多一行
['line 1', 'line 2', 'line 3']
['line 1\n', 'line 2\r\n', 'line 3\n']
['line 1', 'line 2\r', 'line 3', '']
4.6.5. 合并字符串:join()
与 x.split(sep)
相反, 函数 sep.join(lst)
则是将一个列表用指定的分割符连接起来,组成一个字符串:
[79]:
data = ["apple", "pear", "orange", "banana"]
s = ' ~ '.join(data)
print(s)
apple ~ pear ~ orange ~ banana
4.6.6. 替换子字符串:replace()
字符串替换方法 x.split(old, new, count)
用 new 来替换(从左向右)前 count 次搜索到的 old 子字符串。
[80]:
x = "This is a string."
y1 = x.replace("s", "t")
y2 = x.replace("s", "t", 2)
print(y1, y2, sep='\n')
Thit it a ttring.
Thit it a string.
4.6.7. 检查首尾匹配:startswith() / endswith()
用 x.startswith(prefix)
与 x.endswith(suffix)
这两个方法来检查字符串首尾端的子字符串是否匹配 prefix 或 suffix。比较常用的情形可能是检查文件的扩展名:
[81]:
files = ["doc-A.txt", "doc-B.md", "Doc-C.txt"]
for f in files:
if f.startswith('doc'):
print(f"doc - {f}")
if f.endswith('txt'):
print(f"txt - {f}")
doc - doc-A.txt
txt - doc-A.txt
doc - doc-B.md
txt - Doc-C.txt
4.6.8. 清除首尾字符:strip()
用 strip()
清除两侧匹配的字符串,或者用 lstrip()
单独清除左侧的,或用 rstrip()
单独清除右侧的。
[82]:
files = ["doc-A.txt", "doc-B.md", "Doc-C.txt"]
for f in files:
s = (f"strip: {f.strip('d'):10}\t"
f"lstrip: {f.lstrip('doc-'):10}\t"
f"rstrip: {f.rstrip('txt')}")
print(s)
strip: oc-A.txt lstrip: A.txt rstrip: doc-A.
strip: oc-B.m lstrip: B.md rstrip: doc-B.md
strip: Doc-C.txt lstrip: Doc-C.txt rstrip: Doc-C.
默认的 strip()
函数会清除字符串两侧的空格——这有时在读取文件时会用到:
[83]:
x = "line 1 \n line 2 \r\n\tline 3\n"
lines = x.splitlines()
lines_strip = [line.strip() for line in lines]
print(lines, lines_strip, sep='\n')
['line 1 ', ' line 2 ', '\tline 3']
['line 1', 'line 2', 'line 3']
4.6.9. 字符串格式化:format() 与 f 字符串
字符串格式化是一种将字符串中的一部分(通常以 {}
标记)以外部数据(比如变量值或者其他字符串)替换的方法。
方法
x.format()
在任何版本的 Python 3 中受到支持带有前缀 f 的格式化字符串(即 f-string 或 f 字符串)在 Python 3.6 开始被支持
字符串还有一种以百分号 %
进行格式化的方法,是从 Python 2 时期延续下来的语法(在 Python 3 中也可以使用)。本文不再介绍这部分内容,有兴趣的读者可以自行查阅。
我们常常需要把变量的值(可能不是字符串类型)嵌入到字符串中,比如:
[84]:
lang, ver = "Python", 3
x = "We are learning " + lang + " " + str(ver) + "."
print(x)
We are learning Python 3.
上例中虽然用加法连接实现了这一点,但的确非常狼狈。
4.6.9.1. format() 方法
Python 支持用 x.format()
的形式来格式化字符串:
[85]:
lang, ver = "Python", 3
x = "We are learning {} {}.".format(lang, ver)
# 也可以按名称访问
y = "We are learning {mylang} {myver}.".format(mylang=lang, myver=ver)
# 甚至可以进行字典展开,参考字典章节
d = {"mylang": lang, "myver": ver}
z = "We are learning {mylang} {myver}.".format(**d)
print(x, x==y, x==z, sep='\n')
We are learning Python 3.
True
True
上面的几种写法都可以得到相同的结果。如你所见,在格式化字符串中,我们用花括号来占位。默认的, x.format()
的输入参数都会被转为字符串格式。
如果要在格式化字符串中打印花括号,请使用双写花括号(比如
{{
)。如果传入的参数是键值对(即
key=value
的形式),请在花括号内注明键名称。这样的优势是代码可读性好。
利用重复的键名或者重复的序号,可以反复使用同一个目标:
[86]:
# 复用 lang 变量
x = "We are learning {0} {1}. I love {0}!".format(lang, ver)
y = "We are learning {mylang} {myver}. I love {mylang}!".format(mylang=lang, myver=ver)
print(x, x==y, sep='\n')
We are learning Python 3. I love Python!
True
一个更复杂的例子是将输入参数转为特定的字符串格式,比如将浮点数转换为保留指定小数位的字符串。这时候需要用到冒号 :
来指定格式——格式指定位于冒号右侧,而键名(如果无则留空)位于冒号左侧:
[87]:
val, digits = 3.1415926535, 5
x = "PI = {:.5f}...".format(val)
y = "PI = {val:.5f}...".format(val=val)
z = "PI = {0:.{1}f}...".format(val, digits)
print(x, x==y, x==z, sep='\n')
PI = 3.14159...
True
True
Python 支持的格式有:
格式声明 |
格式示例 |
解释 |
输入 |
输出 |
---|---|---|---|---|
|
|
字符串,以最小宽5位输出 |
|
” abc” |
任意数 |
||||
|
|
科学计数法小数点后2位(默认6) |
|
“1.23e+03” |
|
/ |
同上,但输出时大写字母 E |
|
“1.23E+03” |
|
|
保留小数点后4位(默认6) |
|
“1234.5670” |
|
/ |
同上,但输出时大写 NAN 与 INF |
|
“INF” |
|
/ |
接受数字,并自行判断输出格式 |
||
|
5位有效数字(自动定点格式) |
|
“1234.6” |
|
|
3位有效数字(自动科学计数格式) |
|
“1.23e+03” |
|
|
(默认6位) |
|
“nan” |
|
|
|
同上,但输出时使用大写的 E、NAN 与 INF |
|
“1.23E+03” |
|
|
正负数均标明符号;保留2位小数 |
|
“+1234.57” |
|
|
正数空格,负数标明负号;保留2位小数 |
|
” 1234.57” |
|
|
百分比,保留2位小数 |
|
” 12.0%” |
整数 |
||||
|
|
十进制整数 |
|
“123” |
|
|
二进制整数 |
|
“1111011” |
|
|
八进制整数 |
|
“173” |
|
|
十六进制整数 |
|
“7b” |
|
/ |
同上,但使用大写的 a~f |
|
“7B” |
格式符 |
||||
|
|
强制右对齐(数字对象默认) |
|
” 123” |
|
以0而不是空格补位 |
|
“0123” |
|
|
|
强制左对齐(其他对象默认) |
|
“123” |
|
|
强制居中对齐 |
|
“123” |
其他 |
||||
|
|
以逗号千分位分隔 |
|
“1,234” |
|
|
显式保留输入类型;如二进制以”0b”开头 |
|
“0b1111011” |
下面是几个例子:
[88]:
# 在数字两侧各添加3个星号
x = 1234
print("{:*^{}d}".format(x, len(str(x))+6))
***1234***
[89]:
# 输入给定十进制数的其他进制形式
x = 123
for base in "dboxX":
print("Type {base}: {val:#{base}}".format(base=base, val=x))
Type d: 123
Type b: 0b1111011
Type o: 0o173
Type x: 0x7b
Type X: 0X7B
4.6.9.2. f 字符串
上面的例子均由 x.format()
方法实现,但是不足之处在于不能方便地直接利用已有的变量值。比如上文中给浮点数保留指定位数的例子,变量 val
与 digits
仍然需要显式地作为 format()
方法的输入参数:
[90]:
val, digits = 3.1415926535, 5
x = "PI = {0:.{1}f}...".format(val, digits)
y = "PI = {myval:.{mydigits}f}...".format(myval=val, mydigits=digits)
print(x, x==y, sep='\n')
PI = 3.14159...
True
当然,采用上文所介绍过字典展开的方式,你可以用先声明一个字典,然后在传入时用双写星号前缀来展开……
[91]:
val, digits = 3.1415926535, 5
d = {"myval": val, "mydigits": digits}
x = "PI = {myval:.{mydigits}f}...".format(**d)
print(x)
PI = 3.14159...
这样, x.format()
方法的输入就显得不是太累赘。
但并不是所有数据都适合写入同一个字典里的。因此,我推荐使用更方便的 f 字符串来格式化字符串,在字符串的左侧引号之前添加字母 f 即可:
[92]:
# Python >= 3.6
val, digits = 3.1415926535, 5
x = f"PI = {val:.{digits}f}..."
print(x)
PI = 3.14159...
与 x.format()
方法一样,f 字符串支持格式化字串的所有格式。f 字符串也允许在花括号内进行合法的 Python 表达式书写:
[93]:
print(f"{2**3:0>4d}")
0008
但是,花括号中的表达式不能显示地含有反斜线:
[94]:
print(f"A multiline string:\n{'a'+'\n'+'b'}")
File "<ipython-input-94-65ada2ec2e1b>", line 1
print(f"A multiline string:\n{'a'+'\n'+'b'}")
^
SyntaxError: f-string expression part cannot include a backslash
你可以通过将带反斜线的值赋值到变量来规避这一点:
[95]:
x = "{}\n{}".format('a', 'b')
print(f"A multiline string:\n{x}")
A multiline string:
a
b
4.6.10. 大小写转换*
Python 提供了丰富的大小写转换支持,包括
全体强制大小写
upper()/lower()
仅首字母大写
capitalize()
每个单词首字母大写
title()
[96]:
x = "It's a string. This is another."
n = len(x) + 10
d = {
"全大写": x.upper(),
"全小写": x.lower(),
"首字母大写": x.capitalize(),
"单词首字母大写": x.title()
}
for k, v in d.items():
print(f"{k:10}\t{v}")
全大写 IT'S A STRING. THIS IS ANOTHER.
全小写 it's a string. this is another.
首字母大写 It's a string. this is another.
单词首字母大写 It'S A String. This Is Another.
注意, title()
方法会将一些并非单词头的字母识别为单词头(比如上例中 It's
的字母 s)。要避免这一情形,可以配合正则表达式处理,参考 官方文档:str.title() 。
4.7. 字典:dict
Python 的字典以键值对(key-value pairs)的形式存储数据:
每一项数据之间用逗号
,
分隔,所有数据外侧用一组花括号{}
包裹每一项数据都是一组键值对,键与值之间用冒号
:
分隔一个字典内,不能存在相同的键名
在 Python >= 3.7 的版本中,字典的项变更为 有序的 (指在循环中被迭代,或被强制转换成序列时,键或键值对的顺序是稳定的),其顺序与创建时每一项被加入的顺序相同。
[97]:
x = {} # 空字典
y = {"a": 1, "b": 3}
print(y)
{'a': 1, 'b': 3}
字典的每一项数据的值可以是任意的数据类型。同时,不同于 JSON 文件中的键,Python 字典的键也可以是大多数类型(而不仅仅是字符串)。
从编程习惯上讲,我并不推荐在字典键中使用字符串以外的其他类型。
字典可以用 len()
来返回其键值对的个数,用 in
来查询一个键是否在字典中:
[98]:
x = {}
print(len(x))
print("a" in x)
0
False
4.7.1. 字典初始化
字典有许多初始化方式。
依次添加键值对
利用
dict()
构造从成对序列数据中构造
显式地传入字面键值
利用
fromkeys()
方法字典解析
最朴素的方式是依次添加键值对。先新建一个空字典,然后依次向内添加键值对:
[99]:
d = {}
d["a"] = 1
d["c"] = 3
d["b"] = 2
print(d)
{'a': 1, 'c': 3, 'b': 2}
在键与值分别存储在两个序列中时,我们可以利用 for 循环:
[100]:
keys = "a", "c", "b"
vals = 1, 3, 2
d = {}
for k, v in zip(keys, vals):
d[k] = v
print(d)
{'a': 1, 'c': 3, 'b': 2}
从成对序列数据中构造要求一种整合的数据存储方式。如果键与值是“成对地”存储在一个序列中,可以直接使用 dict()
来进行初始化:
[101]:
data = [["a", 1], ["c", 3], ["b", 2]]
d = dict(data)
print(d)
{'a': 1, 'c': 3, 'b': 2}
在字典数据不多时,也可能考虑显式地传入字面键值(键自动视为字符串):
[102]:
d = dict(a=1, c=3, b=2)
print(d)
{'a': 1, 'c': 3, 'b': 2}
使用 fromkeys()
方法能够快速初始化已知键名的字典,将所有键都赋同一个初始值(比如 None
):
[103]:
keys = "a", "c", "b"
d = {}.fromkeys(keys, None)
print(d)
{'a': None, 'c': None, 'b': None}
类似于列表解析,字典也支持解析:
[104]:
data = [["a", 1], ["c", 3], ["b", 2]]
d1 = {x[0]: x[1] for x in data}
keys = "a", "c", "b"
vals = 1, 3, 2
d2 = {k: v for k, v in zip(keys, vals)}
print(d1, d1==d2)
{'a': 1, 'c': 3, 'b': 2} True
4.7.2. 字典的视图
由于字典有键、值、项(键值对)这三个概念,Python 也提供了对应的三种视图:
键视图:
x.keys()
,一个依序的、每个键为一个元素的序列值视图:
x.values()
,一个与键视图中的键依次对应的值组成的序列项视图:
x.items()
,一个依上述顺序的、每个元素是一个键值对元组的序列
用 for 循环来展示一下这三种视图:
[105]:
x = {"a": 1, "c": 3}
for k in x.keys():
print(f"key: {k}")
for v in x.values():
print(f"val: {v}")
for i in x.items():
print(f"item: {i}")
key: a
key: c
val: 1
val: 3
item: ('a', 1)
item: ('c', 3)
在 x.keys()
中循环其实与在 x
中循环的结果是相同的,都是遍历字典的键:
[106]:
for k in x:
print(k)
a
c
字典的项视图 items()
常常被用在 for 循环中,解包成两个循环变量:
[107]:
x = {"a": 1, "c": 3}
for k, v in x.items():
print(f"{k} = {v}")
a = 1
c = 3
4.7.3. 按键索引值:get() / setdefault()
要按字典键索引其对应的值,有以下几种方法:
如果确认键 key 位于字典 x 中,可以直接使用该键来索引
x[key]
如果键 key 可能不在 x 中:
用
x.get(key, default=None)
方法,失败时它会返回一个备用值default
用
x.setdefault(key, default=None)
方法,失败时它会把键值对key: default
添加到字典
[108]:
x = {"a": 1, "c": 3}
print(x["a"])
1
更安全的选择是使用 x.get(key, default=None)
方法。它的作用是:如果 key 存在于 x 的键中,那么返回该键对应的值;否则,返回 default 指定的值。
[109]:
x = {"a": 1, "c": 3}
print(x.get("b", "Not in dict"))
Not in dict
另一个选择是利用 x.setdefault(key, default=None)
方法。如果 key 存在于 x 的键中,那么返回该键对应的值;否则,将 default 值与 key 键组成键值对,加入到字典 x 中。
[110]:
x = {"a": 1, "c": 3}
y1 = x.setdefault("b", 2)
y2 = x["b"] # 键值对已经被添加
print(y1, y2)
2 2
4.7.4. 删除项:pop() / popitem()
最简单的自然是 del()
删除函数:
[111]:
x = {"a": 1, "c": 3, "b": 2}
del(x["a"])
print(x)
{'c': 3, 'b': 2}
字典的 x.pop(key)
与列表在表现上类似,也是返回一个值的同时将其从容器中删除:
[112]:
x = {"a": 1, "c": 3, "b": 2}
y = x.pop("a")
print(x, y)
{'c': 3, 'b': 2} 1
特别指出,方法 pop()
也有一个名为 default
的参数,会在字典不包含键时返回该 default 值(如果不显示地给出 default 的值,那么会造成 KeyError)。这一点与 setdefault()
方法相映成趣。
[113]:
x = {"a": 1, "c": 3, "b": 2}
y = x.pop("a", 10) # 正常返回 x["a"],并删除键 "a"
z = x.pop("a", 100) # 没有键 "a",返回默认值 100
print(x, y, z)
{'c': 3, 'b': 2} 1 100
最后,介绍 popitem()
,这个方法被用到的较少。它并不指定键名来抛出一个项,而是按照字典键的顺序,反向地依次抛出字典地项——即后进先出(LIFO),如同对栈容器进行弹栈操作一样。
对于 Python 3.7 之前的版本,它并不是依照 LIFO 顺序弹出字典项的,而是按照随机选择的顺序。
[114]:
x = {"a": 1, "c": 3, "b": 2}
while len(x) > 0:
y = x.popitem()
print(y)
('b', 2)
('c', 3)
('a', 1)
4.7.5. 更新字典:update()
通过 x.update(y)
,字典 x 可以根据字典 y 的键值对来就地更新字典 x 的数据:
在字典 x 与 y 中都存在的键,以 y 中的值为准
仅在一个字典中存在的键,得以保留
[115]:
x = {"a": 1, "c": 3, "b": 2}
y = {"a": -10, "d": 4}
x.update(y)
print(x)
{'a': -10, 'c': 3, 'b': 2, 'd': 4}
利用循环,我们可以实现与 update()
方法相同的效果:
[116]:
x = {"a": 1, "c": 3, "b": 2}
y = {"a": -10, "d": 4}
for k in y:
x[k] = y[k]
print(x)
{'a': -10, 'c': 3, 'b': 2, 'd': 4}
4.7.6. 更改键名*
字典并没有单独提供更改键名的方法,但这也是一个实用场景。由于 Python >= 3.7 版本引入了字典键的顺序,要在更改键名时保持键的顺序也显得重要。
先说明一种会打乱键顺序的键名更改方法,那就是将指定键用 pop(oldkey)
弹出然后赋值给 d[newkey]
。下面以将键名改为小写为例:
[117]:
d = dict(a=1, B=2, c=3, D=4)
dkey = dict(B="b", D="d")
for oldkey, newkey in dkey.items():
d[newkey] = d.pop(oldkey)
print(d)
{'a': 1, 'c': 3, 'b': 2, 'd': 4}
可以看到,字典 d 的键顺序也被打乱了。要保留顺序,只能遍历所有的字典键:
[118]:
d = {"a": 1, "B": 2, "c": 3, "D": 4}
dkey = {"B": "b", "D": "d"}
oldkeys = tuple(d.keys())
for k in oldkeys:
# 如果有新键名则使用 dkey[k],否则使用旧键名 k
d[dkey.get(k, k)] = d.pop(k)
print(d)
{'a': 1, 'b': 2, 'c': 3, 'd': 4}
4.8. 集合
Python 中的集合借鉴了数学意义上的集合概念,要求内部的元素不能重复。
集合 set 是可变的;Python 也提供了一种不可变集合 frozenset ,其不涉及可变性部分的用法与 set 大致相同。
集合用花括号括起的、逗号分隔的多个项来表示。但是,空集合不能使用 {}
来指明(因为这代表空字典),请使用 set()
指明。
[119]:
x = set([1, 2, 3, 2])
y = {1, 2, 3}
z = set() # 空集合
print(x, y, z)
{1, 2, 3} {1, 2, 3} set()
集合同样支持长度度量 len()
,包含性检查 in
,以及循环遍历。
[120]:
x = {1, 2, 3}
print(len(x), 4 in x)
for k in x:
print(k, end=' ')
3 False
1 2 3
4.8.1. 集合运算与包含关系
集合运算包括交集、并集、差集、对称差集,包含关系包括(真)子集、(真)超集。
运算 |
说明 |
运算符 |
示例 |
集合方法 |
示例 |
---|---|---|---|---|---|
交 |
|
|
同时位于两个集合中的元素 |
|
|
并 |
|
|
至少位于其一集合中的元素 |
|
|
差 |
|
|
只位于 a 而不位于 b 的元素 |
|
|
对称差 |
|
|
位于且仅位于其一集合中的元素 |
|
|
子集 |
|
|
a 的所有元素都在 b 中 |
|
|
真子集 |
|
|
a 是 b 的子集且 a、b 不同 |
/ |
/ |
超集 |
|
|
b 的所有元素都在 a 中 |
|
|
真超集 |
|
|
b 是 a 的子集且 a、b 不同 |
/ |
/ |
互斥 |
/ |
/ |
a 与 b 没有共同元素 |
|
|
注意,上述命令中:
交、并、差可以输入多个集合(如最后一列所示)。如果使用运算符,则用重复使用相同的运算符连接即可。
对于“运算符”这一列命令,变量 a 与 b 都必须是集合(或者不可变集合)类型。对于“集合方法”这一列命令,只有 a 必须是严格的集合(或者不可变集合)类型,b 可以是任意的可迭代对象。
上述集合方法也可以用
set
来调用。例如,取差集可以写为set.difference(a, b)
。上述运算的返回值的结果均以 a 为准。例如,如果 a 是 set 类型而 b 是 frozenset 类型,那么返回值将是 set 类型。
[121]:
a, b = {1, 2, 3}, {2, 4, 5}
set_funcs = {
"交": set.intersection,
"并": set.union,
"差": set.difference,
"对称差": set.symmetric_difference,
"子集": set.issubset,
"超集": set.issuperset,
"互斥": set.isdisjoint
}
for text, func in set_funcs.items():
print(f"{text:8}\t{func(a, b)}")
交 {2}
并 {1, 2, 3, 4, 5}
差 {1, 3}
对称差 {1, 3, 4, 5}
子集 False
超集 False
互斥 False
4.8.2. 更新集合
集合的更新是就地更新,也涉及到上面的集合运算:
直接更新
a.update(b, ...)
,即将并集赋回,等同于a |= b | ...
只保留交集
a.intersection_update(b, ...)
,即将交集赋回,等同于a &= b & ...
只保留差集
a.difference_update(b, ...)
,等同于a -= b | ...
只保留对称差集
a.symmetric_difference_update(b)
,等同于a ^= b
以上命令中,除了对称差以外的运算都可以输入多个集合。
[122]:
a, b = {1, 2, 3}, {2, 4, 5}
set_funcs = {
"(并)更新": set.update,
"交更新": set.intersection_update,
"差更新": set.difference_update,
"对称差更新": set.symmetric_difference_update
}
for text, func in set_funcs.items():
a_copy = a.copy() # 拷贝一个副本,避免直接对 a 改动
func(a_copy, b)
print(f"{text:8}\t{a_copy}")
(并)更新 {1, 2, 3, 4, 5}
交更新 {2}
差更新 {1, 3}
对称差更新 {1, 3, 4, 5}
4.8.3. 增删元素
本节中的命令只对可变集合(set 类型)有效,对不可变集合(frozenset 类型)无效 。
增加:
add(elem)
移除:
移除指定:
remove(elem)
:移除一个不存在集合中的元素时会造成 KeyErrordiscard(elem)
:尝试移除一个元素,如果不存在集合中则静默
随机移除:
pop()
,随机返回一个集合中的元素,并将其从集合中移除清空:
clear()
[123]:
a = {1, 2, 3}
a.add(4)
print("After add\t", a)
a.remove(3)
print("After remove\t", a)
a.discard(100)
print("After discard\t", a)
After add {1, 2, 3, 4}
After remove {1, 2, 4}
After discard {1, 2, 4}
其中, pop()
与 clear()
十分易懂,这里就不再用代码说明了。
4.9. 其他
Python 中提供了一个比较奇怪的变量,即单下划线 _
—— 虽然从理论上讲,下划线是变量名中可以使用的合法字符,这个变量名本应如同单字符变量名 x
、s
一样自然。
它的作用一般有:
在交互式 Python 运行环境中,它会自动记录最后一次代码执行的结果。
习惯上,我们将不需要用到的变量值用该变量标记。例如:
不使用的返回值:
x, _ = divmod(7, 2)
不使用的循环变量值:
[None for _ in range(3)]
不使用的匿名变量输入值:
lambda _: 1
避免编辑器的语法检查。Python 语法检查器会忽略
_
变量;而其他变量如果声明而未在后文引用,检查器会发出警告。