列表和元组
创建列表
列表(list)是一种有序的多个元素的集合,是 Python 中最基本的数据结构之一。最简单的创建列表的方法是,使用方括号 [ ],在方括号内放置列表的元素,元素之间用逗号分隔,比如:
fruits = ["苹果", "香蕉", "桔子"]
numbers = [1, 2, 3, 4, 5]
mixed = [1, "苹果", 3.5]
我们也会经常使用 list() 函数创建列表。它可以把其它可迭代对象转换为列表,比如:
# 从元组创建列表
tup = (1, 2, 3)
list_from_tuple = list(tup) # 结果: [1, 2, 3]
# 从字符串创建字符列表
string = "hello"
list_from_string = list(string) # 结果: ['h', 'e', 'l', 'l', 'o']
这里只需要记住这个 list() 函数即可,后文还会再详细介绍可迭代对象和元组的概念。
列表推导式也是一种最常用的创建列表的方法,不过它稍微复杂一些,我们也同样留到后面再讲解。
访问列表元素
索引
与字符串的索引非常相似。列表是有序的,每个元素都有一个唯一的索引,从 0 开始计数。我们可以使用索引访问列表中的特定元素。
fruits = ["苹果", "香蕉", "桔子", "菠萝"]
print(fruits[0]) # 输出: 苹果
print(fruits[2]) # 输出: 桔子
索引数值可以是负数,负索引意味着从列表的末尾开始计数。例如,-1 是最后一个元素的索引,-2 是倒数第二个元素的索引,依此类推。
fruits = ["苹果", "香蕉", "桔子", "菠萝"]
print(fruits[-1]) # 输出: 菠萝
print(fruits[-2]) # 输出: 桔子
切片
同样与字符串类似,列表也可以做切片操作,得到列表的子集。切片操作使用冒号 : 分隔开始和结束位置。开始位置是包含的,结束位置是不包含的。如果开始位置缺失,表示从源列表最左端开始取数据;如果结束位置缺失,表示选取至源列表的最右端。
fruits = ["苹果", "香蕉", "桔子", "菠萝"]
# 获取第2到第4个元素 (索引 1, 2, 3)
print(fruits[1:4]) # 输出: ['香蕉', '桔子', '菠萝']
# 获取开始到第3个元素
print(fruits[:3]) # 输出: ['苹果', '香蕉', '桔子']
# 获取第2个元素到最后
print(fruits[1:]) # 输出: ['香蕉', '桔子', '菠萝']
在切片操作中,可以再增加一个步进值参数(第三个参数),用于指定获取元素的间隔。比如:
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(numbers[::2]) # 步进 2,取偶数,输出: [0, 2, 4, 6, 8]
这为我们提供了一个非常简洁的方法,把列表中的数据反向排列:只要把步进值设为 -1 即可:
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(numbers[::-1]) # 反向排列,输出: [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
步进值可以简化某些操作,但若同时截取列表的一部分并设置步进值,可能会使操作变得复杂且不易理解。建议尽量避免此类操作。以下是一个复杂示例,读者能否在不运行代码的情况下推算出其结果?
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(numbers[-2:2:-3]) # 输出: ??
与索引和切片相比,Pythora 星球的居民通常使用解包操作来读取列表内的数据。
解包
解包(Unpacking),也叫拆包。它是 Python 中的一种方便的把列表中的元素“解包”(即分解)到变量中的方法。这意味着我们可以在单个操作中,将列表中的多个元素赋值给多个变量。比如:
numbers = [1, 2, 3]
a, b, c = numbers
print(a) # 1
print(b) # 2
print(c) # 3
在上面的例子中,列表 numbers 包含三个元素。通过列表解包,我们把这三个元素分别赋值给 变量 a、b、c。如果只对列表中的某几个元素感兴趣,也可以部分解包:使用一元 * 运算符来表示“剩余的所有元素”:
numbers = [1, 2, 3, 4, 5]
a, b, *rest = numbers
print(a) # 1
print(b) # 2
print(rest) # [3, 4, 5]
在上面的例子中,变量 a 和 b 分别取了列表的前两个元素,而变量 rest 成为了一个包含剩余元素的新列表。在解包时,还可以忽略某些值,使用下划线 _ 作为一个“丢弃”的变量。下划线 _ 通常被用作占位符,表示不需要的变量或参数:
numbers = [1, 2, 3, 4, 5]
a, _, _, _, e = numbers
print(a) # 1
print(e) # 5
在这个例子中,我们只关心列表的第一个和最后一个元素,中间的元素被赋值给占位符。解包还可以应用于嵌套列表,这意味着可以直接从嵌套的列表结构中提取值。比如:
nested_list = [[1, 2], [3, 4]]
(a, b), (c, d) = nested_list
print(a, b, c, d) # 1 2 3 4
解包与索引和切片有着类似的功能,但是在可能的情况下,应该尽量使用解包,而不是索引和切片。与索引和切片相比,解包可以更清晰地表示需要提取哪几个元素,直接为它们赋予有意义的变量名。因此,解包可以使代码更加简洁易读。
修改列表
索引
列表是可变的,我们可以修改、添加或删除其中的元素。若要修改单个值,可直接通过索引指定并赋新值:
fruits = ["苹果", "香蕉", "桔子"]
fruits[0] = "葡萄"
print(fruits) # 输出: ['葡萄', '香蕉', '桔子']
切片
类似的,我们可以通过切片来替换列表的一部分元素:
fruits = ["苹果", "香蕉", "桔子"]
fruits[1:3] = ["桃子", "蓝莓"]
print(fruits) # 输出: ['苹果', '桃子', '蓝莓']
需要注意的是,通过切片替换时,新的子列表元素的数量可以与原切片不同。这意味着我们可以用切片来插入或删除列表中的元素。
fruits = ["苹果", "香蕉", "桔子"]
# 插入元素
fruits[1:1] = ["西瓜", "芒果"]
print(fruits) # 输出: ['苹果', '西瓜', '芒果', '香蕉', '桔子']
# 删除元素
fruits[1:3] = []
print(fruits) # 输出: ['苹果', '香蕉', '桔子']
在修改列表数据的时候,尽量不要使用步进值,否则会使程序难以理解。
嵌套列表
列表里面的元素的数据类型也可以不同,比如: [1, "苹果", 3.5]
元素也可以是另一个list,比如 [1, "苹果", [5, 6, 7]]
我们经常用嵌套列表来表示矩阵、多维数组等数据结构。嵌套列表的使用索引,一层一层打开即可访问其中的数据。比如:
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
print(matrix[1][2]) # 输出:6
matrix[1][2] = 10
print(matrix[1][2]) # 输出:10
列表的运算
连接
使用 + 运算符可以将两个列表连接(Concatenation)起来:
list1 = [1, 2, 3]
list2 = [4, 5, 6]
combined_list = list1 + list2
print(combined_list) # 输出: [1, 2, 3, 4, 5, 6]
重复
使用二元 * 运算符可以重复(Repetition)列表中的元素。
list1 = ["a", "b"]
repeated_list = list1 * 3
print(repeated_list) # 输出: ['a', 'b', 'a', 'b', 'a', 'b']
需要注意的是,* 运算符产生的多份复制是浅拷贝。所谓浅拷贝的意思是:它复制生成了新的列表,却不会复制列表内的元素。对于嵌套列表或包含其他可变数据的列表,新复制的列表中的元素依然还是指向原来列表中的元素的。比如:
elem = ["a"]
row_list = elem * 3
print(row_list) # 输出: ['a', 'a', 'a']
board_list = [row_list] * 3
print(board_list) # 输出: [['a', 'a', 'a'], ['a', 'a', 'a'], ['a', 'a', 'a']]
board_list[0][0] = 0
print(board_list) # 输出: [[0, 'a', 'a'], [0, 'a', 'a'], [0, 'a', 'a']]
当我们改变 board_list[0][0] 的值的时候,其它几个行列表中的值也被改变了。因为这些复制产生的列表实际上都是同一个列表。
若需生成深拷贝数据(即每一行数据相互独立),可以使用列表推导式初始化多维列表。比如,我们需要生成一个 8*8 的棋盘,棋盘每个格内的初始数据都是 0,那么需要这样写:
board = [[0] * 8 for _ in range(8)]
每一行内的元素是整数,是不可变类型,因此可以直接使用 * 运算符复制多份;但不同的行之间,不能复制。
检查数据是否存在
检查一个元素是否在列表中可以使用 in 关键字。这会返回一个布尔值,指示元素是否存在于列表中。这个操作也叫成员运算。
下面是一个简单的例子:
my_list = [1, 2, 3, 4, 5]
print(3 in my_list) # 输出: True
print(6 in my_list) # 输出: False
print(7 not in my_list) # 输出: True
in 关键字也可以被用在链式比较中,比如:
x = 3
my_list = [1, 2, 3, 4, 5]
print(2 < x in my_list) # 输出: True
但有时候,这样的代码很容易产生迷惑,尽量不要这样做,比如:
print(False == False in [False]) # 输出: True
上面的程序是一个链式比较操作,因此结果为 True,但不熟悉的读者可能会考虑,无论是 == 优先级高,还是 in 优先级高,结果都应该是 False 嘛。
长度
使用函数 len() 可以获取列表的长度,也就是包含几个元素,比如:
numbers = [1, 2, 3, 4, 5, 6]
print(len(numbers)) # 输出: 6
len() 不仅可以返回字符串和列表的长度,也可以用于得到元组、字典、集合等其它一些数据类型的长度。
最大最小值
使用函数 max() 和 min() 函数可以获取列表中元素的最大最小值。比如:
numbers = [34, 12, 89, 5, 73, 23]
# 使用 max(list) 返回列表中的最大值
max_value = max(numbers)
print(f"列表中最大值是:{max_value}") # 输出: 89
# 使用 min(list) 返回列表中的最小值
min_value = min(numbers)
print(f"列表中最小值是:{min_value}") # 输出: 5
与 len() 函数类似,max() 和 min() 函数也可以被应用于字符串、元组等数据类型。
求和
sum() 函数用于计算返回列表中所有元素的总和。比如:
numbers = [1, 2, 3, 4, 5]
total = sum(numbers)
print(total) # 输出: 15
sum() 还接受一个可选的 start 参数,这个参数的值会加到总和中。默认情况下,start 的值为 0。
numbers = [1, 2, 3, 4, 5]
total = sum(numbers, 10)
print(total) # 输出: 25
常用的列表方法
与字符串类似,列表也是一种对象,也有它的方法。下面代码可以列出列表数据对象全部的属性和方法:
print(dir([]))
修改列表元素
Python 程序中,最常用的修改列表元素的方式还是利用索引和切片。不过,列表的方法也有其优势,它有方法名,可以直接看出来所做的是什么操作,程序可读性更好。经常被用来改变列表元素的列表方法包括:
- append() - 向列表末尾添加一个元素。
- extend() - 将另一个列表(或任何可迭代对象)的元素添加到当前列表的末尾。
- insert() - 在指定索引处插入一个元素。
- remove() - 删除指定的元素。
- pop() - 删除指定索引处的元素并返回它,如果不指定索引,就删除数组的最后一个元素。这个方法与 remove() 的区别在于,remove() 的输入是一个元素的值,pop() 的输入是一个元素的索引。
fruits = ["苹果", "香蕉", "桔子"]
fruits.append("草莓")
print(fruits) # 输出: ['苹果', '香蕉', '桔子', '草莓']
fruits.extend(["西瓜", "芒果"])
print(fruits) # 输出: ['苹果', '香蕉', '桔子', '草莓', '西瓜', '芒果']
fruits.insert(1, "鸭梨")
print(fruits) # 输出: ['苹果', '鸭梨', '香蕉', '桔子', '草莓', '西瓜', '芒果']
fruits.remove("西瓜")
print(fruits) # 输出: ['苹果', '鸭梨', '香蕉', '桔子', '草莓', '芒果']
removed_fruit = fruits.pop(2)
print(removed_fruit) # 输出: 香蕉
print(fruits) # 输出: ['苹果', '鸭梨', '桔子', '草莓', '芒果']
print(fruits.pop()) # 输出: '芒果'
这其中,append() 方法是最常用的,我们经常会在程序中创建一个空的列表,然后再循环结构中,不断使用 append() 把数据添加进列表。等介绍完循环语句后,我们会给出相应的示例。
排序
sort() 方法用于对列表中的元素进行排序。默认情况下,sort() 方法会按照升序对列表进行排序,但也可以接受参数来自定义排序方式。sort() 方法会修改原来的列表,也就是说,它不会创建一个新的排序后的列表,而是直接在原来的列表上进行排序。
sort() 方法接收两个参数。reverse 参数如果设置为 True,sort() 方法将进行降序排序。key 参数来指定一个函数,这个函数会在每个元素上被调用,其返回值将作为排序的依据。Python 中有 一个内置的通用排序函数 sorted(),它与列表的 sort() 方法非常类似,也可以用于把列表排序,也有个 key 参数。其实上文介绍过的 max() 和 min() 函数也有个类似的 key 参数, 关于这个 key 参数的使用方法,我们将在后文介绍过相关基础知识后,在高阶函数 sorted一节一并介绍。
这里我们只看几个最基本的示例:
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
numbers.sort()
print(numbers) # 输出: [1, 1, 2, 3, 4, 5, 6, 9]
numbers.sort(reverse=True)
print(numbers) # 输出: 降序排列 [9, 6, 5, 4, 3, 2, 1, 1]
查找元素
index() 方法的名字比较迷惑,它并不是对列表做索引,而是在列表中搜索一个元素,找到返回这个元素的索引。
numbers = [34, 12, 89, 5, 12, 73, 23, 12]
print(numbers.index(12)) # 输出: 1
指定的元素可能在列表中重现了多次,但 index() 方法只会返回第一次出现的索引位置。
元素出现个数
count() 方法可以返回指定元素在列表中出现的次数,比如:
numbers = [34, 12, 89, 5, 12, 73, 23, 12]
print(numbers.count(12)) # 输出: 3
count() 只计算一个特定元素出现的次数,如果需要统计列表中每个元素出现的次数,可以参考统计次数一节的介绍。
反转列表
reverse() 方法可以反转列表中的元素。比如:
numbers = [1, 2, 3, 4, 5]
numbers.reverse()
print(numbers) # 输出: [5, 4, 3, 2, 1]
reverse() 方法的功能,与上文介绍的步进值设为 -1 的切片的功能相似。区别是,切片会生成一个新的列表,而 reverse() 方法是直接在源列表上做改动。
清空列表
clear() 方法可以移除列表中的所有元素。比如:
numbers = [1, 2, 3, 4, 5]
numbers.clear()
print(numbers) # 输出: []