1. 序列概述
在 Python 中,我们经常需要处理和组织多个数据,而不是仅对少量数据进行操作。内置容器就是为我们提供了方便的方式来存储和管理数据的集合。
之前学习过的数字、布尔、字符串、复数、空值都是 Python 的内置基本数据类型。Python 中内置容器有四种:
- 列表(list)—— 可变、有序
- 元组(tuple)—— 不可变、有序
- 集合(set)—— 可变、无序、无重复
- 字典(dict)—— 可变、键值对
序列是一种特殊的内置容器,主要强调元素的有序性和可迭代性:
- 有序性:不是指排序问题,而是指元素的出入顺序是可以确定的。
- 可迭代性:按照一定的顺序进行访问,序列其实就是一个线性表。
字符串、列表、元组都属于序列类型,它们共享许多通用操作(索引、切片、拼接、重复、成员运算等)。
2. 字符串
2.1 创建字符串
字符串就是由零个或多个字符组成的有限序列。使用引号(单引号、双引号和三引号都可以)来创建字符串。
s1 = 'hello, world!'
s2 = "你好,世界!❤️"
s3 = '''hello,
wonderful
world!'''
print(s1)
print(s2)
print(s3)
2.2 转义字符
我们可以在字符串中使用 \(反斜杠)来表示转义,也就是说 \ 后面的字符不再是它原来的意义。例如:\n 表示换行;\t 表示制表符。如果字符串本身包含了 '、"、\ 这些特殊的字符,必须通过 \ 进行转义处理。
s1 = '\'hello, world!\''
s2 = '\\hello, world!\\'
print(s1) # 'hello, world!'
print(s2) # \hello, world!\
2.3 原始字符串
Python 中有一种以 r 或 R 开头的字符串,这种字符串被称为原始字符串,意思是字符串中的每个字符都是它本来的含义,没有所谓的转义字符。
s1 = '\\it \\is \\time \\to \\read \\now'
s2 = r'\it \is \time \to \read \now'
print(s1)
print(s2)
2.4 字符的特殊表示
Python 中还允许在 \ 后面跟一个八进制或者十六进制数来表示字符,例如 \141 和 \x61 都代表小写字母 a。另外一种表示字符的方式是在 \u 后面跟 Unicode 字符编码。
s1 = '\141\142\143\x61\x62\x63'
s2 = '\u7a0b \u5e8f \u8bbe \u8ba1'
print(s1) # abcabc
print(s2) # 程 序 设 计
2.5 ord() 和 chr()
对于单个字符的编码,Python 提供了 ord() 函数获取字符的整数表示,chr() 函数把编码转换为对应的字符。
print(ord('A')) # 65 — ASCII码的十进制数
print(ord('中')) # 20013 — Unicode编码对应的十进制数
print(chr(66)) # 'B'
print(chr(25991)) # '文'
2.6 Unicode 与编码
Unicode 是一个字符集,为世界上几乎所有的字符分配了唯一的编号(码点)。ord() 函数返回的就是字符对应的 Unicode 编码。
UTF-8 是一种针对 Unicode 的可变长度字符编码,它将 Unicode 码点编码成 1 到 4 个字节的序列。以 Unicode 表示的 str 通过 encode() 方法可以编码为指定的 bytes。
print('中'.encode('utf-8')) # b'\xe4\xb8\xad'
print('ABC'.encode('ascii')) # b'ABC'
print('中文'.encode('utf-8')) # b'\xe4\xb8\xad\xe6\x96\x87'
print(len('中')) # 1 — 一个字符
print(len('中'.encode('utf-8'))) # 3 — UTF-8编码,中文占三个字节
print(len('中'.encode('gb2312'))) # 2 — GB2312编码,中文占两个字节
2.7 字符串的运算
Python 语言为字符串类型提供了非常丰富的运算符。我们可以使用 + 运算符来实现字符串的拼接,使用 * 运算符来重复一个字符串的内容,使用 in 和 not in 来判断一个字符串是否包含另外一个字符串。
拼接和重复
s1 = 'hello' + ', ' + 'world'
print(s1) # hello, world
print('!' * 3) # !!!
比较运算
字符串的大小比较比的是每个字符对应的编码的大小。例如 A 的编码是 65,而 a 的编码是 97,所以 'A' < 'a' 的结果是 True。
print(s1 == s2) # False
print(s1 < s2) # True
print('boy' < 'bad') # False(比较第二个字符'o'<'a'为False)
print(ord('中')) # 20013
print(ord('文')) # 25991
成员运算
用 in 和 not in 判断一个字符串中是否包含另外一个字符或字符串。
s1 = 'hello, world'
print('wo' in s1) # True
print('ll' not in s1) # False
print("程序" in "Python 程序设计") # True
2.8 索引和切片
字符串是一种有序序列,可以通过正向或反向的整数索引访问其中的元素。字符串是不可变类型,不能通过索引运算修改字符串中的字符。
s = 'abc123456'
n = len(s)
print(s[0], s[-n]) # a a
print(s[n-1], s[-1]) # 6 6
print(s[2:5]) # c12
print(s[-7:-4]) # c12
print(s[2:]) # c123456
print(s[:2]) # ab
print(s[::2]) # ac246
print(s[::-1]) # 654321cba
2.9 字符的遍历
使用 for-in 循环遍历字符串中的每个字符,有两种方式:
# 方式一:通过索引遍历
s = 'hello'
for i in range(len(s)):
print(s[i])
# 方式二:直接遍历
s = 'hello'
for elem in s:
print(elem)
2.10 字符串的方法
通过字符串类型自带的方法对字符串进行操作和处理,语法是 foo.bar()。
大小写相关操作
s1 = 'hello, world!'
print(s1.capitalize()) # Hello, world! — 首字母大写
print(s1.title()) # Hello, World! — 每个单词首字母大写
print(s1.upper()) # HELLO, WORLD! — 全部大写
s2 = 'GOODBYE'
print(s2.lower()) # goodbye — 全部小写
说明:由于字符串是不可变类型,使用字符串的方法对字符串进行操作会产生新的字符串,原变量的值并没有发生变化。
查找操作
s = 'hello, world!'
print(s.find('or')) # 8
print(s.find('or', 9)) # -1 表示没有找到
print(s.index('or')) # 8
# s.index('or', 9) # ValueError: substring not found
# 逆向查找
s = 'hello world!'
print(s.find('o')) # 4
print(s.rfind('o')) # 7
find 方法找不到指定的字符串会返回 -1,index 方法找不到会引发 ValueError 错误。
性质判断
s1 = 'hello, world!'
print(s1.startswith('He')) # False
print(s1.startswith('hel')) # True
print(s1.endswith('!')) # True
s2 = 'abc123456'
print(s2.isdigit()) # False — 判断是否全由数字构成
print(s2.isalpha()) # False — 判断是否全由字母构成
print(s2.isalnum()) # True — 判断是否由字母和数字构成
修剪操作
strip() 方法可以修剪掉左右两端指定字符(默认是空格),lstrip() 仅去左侧,rstrip() 仅去右侧。
s1 = ' jackfrued@126.com '
print(s1.strip()) # jackfrued@126.com
s2 = '~你好,世界~'
print(s2.lstrip('~')) # 你好,世界~
print(s2.rstrip('~')) # ~你好,世界
替换操作
s = 'hello, good world'
print(s.replace('o', '@')) # hell@, g@@d w@rld
print(s.replace('o', '@', 1)) # hell@, good world
拆分与合并
s = 'I love you'
words = s.split()
print(words) # ['I', 'love', 'you']
print('~'.join(words)) # I~love~you
s = 'I#love#you#so#much'
words = s.split('#')
print(words) # ['I', 'love', 'you', 'so', 'much']
words = s.split('#', 2)
print(words) # ['I', 'love', 'you#so#much']
split + input 一行输入多个变量
x, y, z = input("请输入三个值,以逗号分隔: ").split(',')
print(f"x的值为: {x}, y的值为: {y}, z的值为: {z}")
# 配合 map() 转换为需要的类型
x, y, z = map(int, input("请输入三个值,以逗号分隔: ").split(','))
s = x + y + z
print(f"和为: {s}")
3. 列表
3.1 列表的定义
列表是一种有序的集合,可以保存多个数据,可以随时添加和删除其中的元素。使用 [] 来定义列表,元素用逗号分隔。
list1 = [35, 12, 99, 68, 55, 35, 87]
list2 = ['Python', 'Java', 'Go', 'Kotlin']
list3 = [100, 12.3, 'Python', True]
# 使用 list() 函数将其他序列变成列表
list4 = list(range(1, 10)) # [1,2,3,4,5,6,7,8,9]
list5 = list('hello') # ['h','e','l','l','o']
3.2 序列的通用操作
(1)索引
也叫做角标、下标,表示元素在序列中的位置。长度为 n 的序列,索引范围 [0, n-1],负数索引范围 [-1, -n]。
arr = [1, 2, 3, 4, 5]
print(arr[0]) # 1
print(arr[4]) # 5
print(arr[-1]) # 5
print(arr[-5]) # 1
(2)切片
快速获取序列中连续的子区间:sequence[start:stop:step],[start, stop),左闭右开。
arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(arr[:]) # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(arr[::2]) # [1, 3, 5, 7, 9]
print(arr[::-1]) # [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] 反向
print(arr[3:]) # [4, 5, 6, 7, 8, 9, 10]
print(arr[3:8]) # [4, 5, 6, 7, 8]
print(arr[2:8:2]) # [3, 5, 7]
print(arr[-6:-2:2]) # [5, 7]
print(arr[-2:-6:-2]) # [9, 7]
(3)连接与重复
list5 = [35, 12, 99, 45, 66]
list6 = [45, 58, 29]
print(list5 + list6) # [35, 12, 99, 45, 66, 45, 58, 29]
list5 += list6
print(list5) # [35, 12, 99, 45, 66, 45, 58, 29]
(4)长度与最值
arr = [1, 3, 2, 4, 6, 8, 9, 4, 2]
print(len(arr)) # 9
print(min(arr)) # 1
print(max(arr)) # 9
(5)成员存在性
使用 in 和 not in 判断元素是否存在于序列中。
3.3 列表对象方法
对象方法必须通过某一个对象来调用:对象.函数名称()
添加元素
arr = []
arr.append(1) # 在表尾添加元素
arr.append(2)
arr.append(3)
print(arr) # [1, 2, 3]
arr.insert(1, 4) # 在指定角标处插入元素
print(arr) # [1, 4, 2, 3]
# extend:将其他序列中的元素依次添加在表尾
arr1 = [1, 2, 3]
arr2 = [4, 5, 6]
arr2.extend(arr1)
print(arr2) # [4, 5, 6, 1, 2, 3]
删除元素
arr4 = [1, 2, 3, 1, 2, 3, 1, 2, 3]
arr4.remove(1) # 删除第一次出现的元素(找不到报错)
print(arr4) # [2, 3, 1, 2, 3, 1, 2, 3]
print(arr4.pop(1)) # 删除指定角标元素并返回
print(arr4.pop()) # 默认删除表尾元素并返回
del arr4[3] # del关键字删除指定元素(不返回)
del arr4[::2] # del配合切片删除
arr4.clear() # 清空列表
排序和反转
arr5 = [5, 4, 9, 1, 8, 2, 3, 7, 6]
arr5.sort() # 默认升序排列
print(arr5) # [1, 2, 3, 4, 5, 6, 7, 8, 9]
arr5.sort(reverse=True) # 降序排列
arr5.reverse() # 反转列表
count() 和 index()
arr6 = [1, 2, 3, 1, 2, 3, 1, 1, 1, 2, 3, 1, 2, 3]
print(arr6.count(1)) # 6 — 统计元素出现次数
print(arr6.index(3)) # 2 — 从左到右查找第一次出现的角标
copy() — 浅层拷贝
arr1 = [1, 2, 3, 4, 5]
arr2 = arr1.copy()
arr1[0] = 666
print(arr1) # [666, 2, 3, 4, 5]
print(arr2) # [1, 2, 3, 4, 5] — 浅拷贝,基本元素不受影响
# 浅拷贝对嵌套列表的影响
arr1 = [1, 2, [3, 4, 5]]
arr2 = arr1.copy()
arr1[2][1] = 666
print(arr1) # [1, 2, [3, 666, 5]]
print(arr2) # [1, 2, [3, 666, 5]] — 嵌套列表共享!
3.4 元素的遍历
# 方法一:通过索引遍历
languages = ['Python', 'Java', 'C++', 'Fortran']
for index in range(len(languages)):
print(languages[index])
# 方法二:直接遍历
for language in languages:
print(language)
3.5 列表生成式
在 Python 中,列表还可以通过一种特殊的生成式语法来创建。
# 普通方式:创建1-99中能被3或5整除的数
items = []
for i in range(1, 100):
if i % 3 == 0 or i % 5 == 0:
items.append(i)
# 列表生成式:一行搞定
items = [i for i in range(1, 100) if i % 3 == 0 or i % 5 == 0]
# 平方列表
nums2 = [num ** 2 for num in nums1]
# 过滤
nums2 = [num for num in nums1 if num > 50]
3.6 嵌套列表
如果列表中的元素也是列表,那么可以称之为嵌套的列表,可以用来表示表格或数学上的矩阵。
scores = [[95, 83, 92], [80, 75, 82], [92, 97, 90],
[80, 78, 69], [65, 66, 89]]
print(scores[0]) # [95, 83, 92]
print(scores[0][1]) # 83
# 用列表生成式创建嵌套列表
import random
scores = [[random.randrange(60, 101) for _ in range(3)] for _ in range(5)]
4. 元组
4.1 元组的定义
元组(tuple)也是多个元素按照一定顺序构成的序列。元组和列表的不同之处在于,元组是不可变类型,一旦定义,其中的元素不能再添加或删除,元素的值也不能修改。定义元组使用 () 字面量语法。
t1 = (35, 12, 98)
t2 = ('骆昊', 45, True, '四川成都')
print(type(t1)) #
print(len(t1)) # 3
# 索引运算
print(t1[0]) # 35
print(t2[-1]) # 四川成都
# 切片运算
print(t2[:2]) # ('骆昊', 45)
# 循环遍历
for elem in t1:
print(elem)
# 成员运算
print(12 in t1) # True
# 拼接运算
t3 = t1 + t2
一元组的陷阱
() 表示空元组,但如果元组中只有一个元素,需要加上一个逗号,否则 () 就是改变运算优先级的圆括号。
a = ()
print(type(a)) #
b = ('hello')
print(type(b)) # — 不是元组!
c = (100)
print(type(c)) # — 不是元组!
d = ('hello',)
print(type(d)) #
e = (100,)
print(type(e)) #
4.2 打包和解包
当我们把多个用逗号分隔的值赋给一个变量时,多个值会打包成一个元组;把一个元组赋值给多个变量时,元组会解包成多个值。
# 打包
a = 1, 10, 100
print(type(a)) #
print(a) # (1, 10, 100)
# 解包
i, j, k = a
print(i, j, k) # 1 10 100
星号表达式
当变量个数少于元素个数时,可以使用星号表达式让一个变量接收多个值(变成列表)。
a = 1, 10, 100, 1000
i, j, *k = a
print(i, j, k) # 1 10 [100, 1000]
i, *j, k = a
print(i, j, k) # 1 [10, 100] 1000
*i, j = a
print(i, j) # [1, 10, 100] 1000
# 解包语法对所有的序列都成立
a, b, *c = range(1, 10)
a, *b, c = 'hello'
交换变量的值
在 Python 中交换两个变量 a 和 b 的值只需要 a, b = b, a,Python 的字节码指令中有专门的指令直接实现,效率非常高。
4.3 元组和列表的比较
- 元组是不可变类型,更适合多线程环境,降低了并发访问变量的同步化开销。
- 创建元组比创建列表更快。用 timeit 模块测试,创建 10000000 次元组耗时约 0.078 秒,而列表耗时约 0.635 秒,有数量级的差别。
- 元组和列表可以相互转换:
list(元组)和tuple(列表)。
5. 集合
5.1 什么是集合
集合(set)是一种容器型数据类型,跟数学上的集合本质上相同,具备以下特性:
- 无序性:集合中每个元素的地位相同,元素之间无序,不支持索引运算。
- 互异性:任何两个元素都不相同,集合中不能有重复元素。
- 确定性:给定一个元素,它要么属于集合,要么不属于。
集合底层使用了哈希存储(散列存储),成员运算在性能上要优于列表。
5.2 创建集合
使用花括号 {} 或 set() 函数创建。空集合必须用 set(),因为 {} 表示空字典。
set1 = {1, 2, 3, 3, 3, 2}
print(set1) # {1, 2, 3} — 自动去重
set2 = set('hello')
print(set2) # {'h', 'e', 'l', 'o'} — 重复的'l'只出现一次
set3 = set([1, 2, 2, 3, 3, 3, 2, 1])
print(set3) # {1, 2, 3}
# 集合生成式
set5 = {num for num in range(1, 20) if num % 3 == 0 or num % 7 == 0}
集合中的元素必须是 hashable 类型(不可变类型),如整数、浮点数、布尔值、字符串、元组。列表、集合等可变类型不能作为集合的元素。
5.3 元素的遍历
set1 = {'Python', 'C++', 'Java', 'Kotlin', 'Swift'}
for elem in set1:
print(elem) # 输出顺序不确定,体现无序性
5.4 集合的运算
成员运算
print(10 in set1) # False
print(15 in set1) # True
交集、并集、差集、对称差
set1 = {1, 2, 3, 4, 5, 6, 7}
set2 = {2, 4, 6, 8, 10}
# 交集 A∩B — 两个集合共同存在的元素
print(set1 & set2) # {2, 4, 6}
print(set1.intersection(set2)) # {2, 4, 6}
# 并集 A∪B — 两个集合所有唯一元素
print(set1 | set2) # {1,2,3,4,5,6,7,8,10}
print(set1.union(set2)) # {1,2,3,4,5,6,7,8,10}
# 差集 A-B — 只在第一个集合中存在的元素
print(set1 - set2) # {1, 3, 5, 7}
print(set1.difference(set2)) # {1, 3, 5, 7}
# 对称差 A△B — 只在其中一个集合中的元素(并集减交集)
print(set1 ^ set2) # {1, 3, 5, 7, 8, 10}
print(set1.symmetric_difference(set2)) # {1, 3, 5, 7, 8, 10}
复合赋值运算
set1 |= set2 # 相当于 set1 = set1 | set2 (并集更新)
set1 &= set2 # 相当于 set1 = set1 & set2 (交集更新)
set2 -= set1 # 相当于 set2 = set2 - set1 (差集更新)
比较运算 — 子集与超集
set1 = {1, 3, 5}
set2 = {1, 2, 3, 4, 5}
set3 = {5, 4, 3, 2, 1}
print(set1 < set2) # True — set1是set2的真子集
print(set1 <= set2) # True — set1是set2的子集
print(set2 > set1) # True — set2是set1的超集
print(set2 == set3) # True — 元素相同即相等(无序!)
print(set1.issubset(set2)) # True
print(set2.issuperset(set1)) # True
5.5 集合的方法
set1 = {1, 10, 100}
# 添加元素
set1.add(1000)
set1.add(10000)
# 删除元素
set1.discard(10) # 元素存在时删除,不存在无操作也不抛异常
if 100 in set1:
set1.remove(100) # 元素不存在时会报KeyError
set1.pop() # 随机删除一个元素并返回
set1.clear() # 清空
# 判断两个集合是否没有交集
set1.isdisjoint(set2) # True表示没有相同元素
5.6 不可变集合 frozenset
frozenset 与 set 的关系如同 tuple 与 list 的关系。由于是不可变类型,能够计算出哈希码,因此它可以作为 set 中的元素。
fset1 = frozenset({1, 3, 5, 7})
fset2 = frozenset(range(1, 6))
print(fset1 & fset2) # frozenset({1, 3, 5})
print(fset1 | fset2) # frozenset({1, 2, 3, 4, 5, 7})
6. 字典
6.1 为什么需要字典
当我们需要一个变量来保存一个人的多项信息(姓名、年龄、身高、体重、住址、手机号等),列表和元组需要记住每个信息在第几个位置,非常不便。字典(dictionary)以键值对的方式把数据组织到一起,可以通过键找到与之对应的值。
6.2 创建字典
使用 {} 字面量语法,每个元素由 : 分隔的两个值构成。
person = {
'name': '王大锤',
'age': 55,
'height': 168,
'weight': 60,
'addr': '成都市武侯区科华北路62号1栋101'
}
# 使用 dict() 构造器
person = dict(name='王大锤', age=55, addr='成都市武侯区科华北路62号1栋101')
# 使用 zip() 创建字典
items1 = dict(zip('ABCDE', '12345'))
print(items1) # {'A': '1', 'B': '2', 'C': '3', 'D': '4', 'E': '5'}
# 字典生成式
items3 = {x: x ** 3 for x in range(1, 6)}
print(items3) # {1:1, 2:8, 3:27, 4:64, 5:125}
添加和更新键值对
# 单个添加
person["爱好"] = "Python编程"
person["月薪"] = 12345
# 批量添加 update()
person.update({"身高": 180, "体重": 75})
6.3 字典的运算
成员运算和索引运算
# 成员运算
print('name' in person) # True
print('tel' in person) # False
# 索引运算
print(person['name']) # 王大锤
print(person['addr']) # 成都市武侯区科华北路62号1栋101
person['age'] = 25 # 修改值
person['tel'] = '13122334455' # 添加新键值对
# 循环遍历
for key in person:
print(f'{key}:\t{person[key]}')
字典中的键必须是不可变类型(整数、浮点数、字符串、元组等),列表、集合、字典不能作为键,但都可以作为值。
6.4 字典的方法
get() — 安全取值
print(person.get('name')) # 王大锤
print(person.get('sex')) # None — 不存在不报错
print(person.get('sex', True)) # True — 可指定默认值
keys()、values()、items()
print(person.keys()) # dict_keys(['name', 'age', 'height'])
print(person.values()) # dict_values(['王大锤', 25, 178])
print(person.items()) # dict_items([('name', '王大锤'), ...])
# 遍历键值对
for key, value in person.items():
print(f'{key}:\t{value}')
update() — 合并字典
person1 = {'name': '王大锤', 'age': 55, 'height': 178}
person2 = {'age': 25, 'addr': '成都市武侯区科华北路62号1栋101'}
person1.update(person2)
print(person1) # {'name': '王大锤', 'age': 25, 'height': 178, 'addr': '...'}
# Python 3.9+ 可用 |=
person1 |= person2
pop()、popitem()、clear()、del
print(person.pop('age')) # 25 — 删除并返回
print(person.popitem()) # ('addr', '...') — 删除并返回键值对元组
person.clear() # 清空所有键值对
del person['age'] # del关键字删除
6.5 字典应用案例
案例1:统计英文字母出现次数
sentence = input('请输入一段话: ')
counter = {}
for ch in sentence:
if 'A' <= ch <= 'Z' or 'a' <= ch <= 'z':
counter[ch] = counter.get(ch, 0) + 1
sorted_keys = sorted(counter, key=counter.get, reverse=True)
for key in sorted_keys:
print(f'{key} 出现了 {counter[key]} 次.')
案例2:筛选股票
stocks = {
'AAPL': 191.88, 'GOOG': 1186.96, 'IBM': 149.24,
'ORCL': 48.44, 'ACN': 166.89, 'FB': 208.09, 'SYMC': 21.29
}
stocks2 = {key: value for key, value in stocks.items() if value > 100}
print(stocks2) # {'AAPL':191.88, 'GOOG':1186.96, 'IBM':149.24, 'ACN':166.89, 'FB':208.09}
7. 推导式
推导式是 Python 的一大特色,它允许你以更简洁的方式创建列表、字典或集合。相较于传统的 for 循环,推导式可以让你用一行代码完成相同任务。
7.1 列表推导式
# 生成 0-4 的列表
x = [i for i in range(5)]
print(x) # [0, 1, 2, 3, 4]
# 过滤出偶数
os_list = [i for i in range(10) if i % 2 == 0]
print(os_list) # [0, 2, 4, 6, 8]
7.2 字典推导式
# 创建数字与字符串的映射
d = {i: str(i) for i in range(5)}
print(d) # {0:'0', 1:'1', 2:'2', 3:'3', 4:'4'}
7.3 集合推导式
# 去除重复项
my_set = {x for x in [1, 2, 2, 3, 4, 5, 5]}
print(my_set) # {1, 2, 3, 4, 5}