列表和元组
Python 列表和元组到底有啥区别?别死记硬背,看场景选就行
核心区别一句话总结:列表可变,元组不可变。这一差异决定了它们各自的应用场景和性能特性。
刚开始学 Python 的时候,我总搞不清列表和元组啥时候用哪个。明明都能存一串东西,干嘛整两个?
后来写多了就明白了:列表是”活”的,元组是”死”的。列表能改能删能加,元组定下来就动不了了。
列表:能增能删能改,最常用的”筐”
列表用方括号 [],啥都能往里面放,数字、字符串、布尔值、甚至另一个列表。
基本定义与创建
# 空列表
empty_list = []
# 包含不同类型元素的列表(Python 列表支持异构元素)
mixed_list = [1, "apple", 3.14, True]
# 嵌套列表(列表中包含列表)
nested_list = [1, [2, 3], [4, [5, 6]]]
访问元素
通过索引(下标)访问元素,索引从 0 开始,支持负数索引(从末尾计数,-1 表示最后一个元素)。
fruits = ["apple", "banana", "cherry", "date"]
# 访问单个元素
print(fruits[0]) # 输出:apple(第一个元素)
print(fruits[-1]) # 输出:date(最后一个元素)
# 分片(切片):获取子列表,语法为 [start:end:step],左闭右开
print(fruits[1:3]) # 输出:['banana', 'cherry'](索引1到2的元素)
print(fruits[:2]) # 输出:['apple', 'banana'](从开头到索引1)
print(fruits[2:]) # 输出:['cherry', 'date'](从索引2到结尾)
print(fruits[::2]) # 输出:['apple', 'cherry'](步长为2,间隔一个元素)
# 复制列表(创建副本,修改副本不影响原列表)
fruits_copy = fruits[:]
# 获取元素对应索引
index = fruits.index(target)
切片这个规则刚开始容易晕,记住
[start:end]取的是 start 到 end-1 就行了。
修改元素
列表是可变的,直接通过索引修改元素:
numbers = [1, 2, 3, 4]
numbers[1] = 20 # 将索引1的元素改为20
print(numbers) # 输出:[1, 20, 3, 4]
添加元素
append(x):在列表末尾添加元素x。insert(index, x):在指定索引index处插入元素x。extend(iterable):将可迭代对象(如列表、元组)的元素添加到末尾。
colors = ["red", "green"]
colors.append("blue") # 末尾添加
print(colors) # 输出:['red', 'green', 'blue']
colors.insert(1, "yellow") # 索引1处插入
print(colors) # 输出:['red', 'yellow', 'green', 'blue']
colors.extend(["purple", "orange"]) # 批量添加
print(colors) # 输出:['red', 'yellow', 'green', 'blue', 'purple', 'orange']
append 追加,insert 插队,extend 合并
append 和 extend 的区别:append 把整个东西当做一个元素加进去,extend 把里面的元素拆开加进去。我刚开始老搞混,后来记住了:append 是”加一个”,extend 是”扩一批”。
删除元素
del list[index]:删除指定索引的元素。list.remove(x):删除第一个值为x的元素(若不存在则报错)。list.pop(index):删除并返回指定索引的元素(默认删除最后一个)。
languages = ["Python", "Java", "C++", "Python"]
del languages[1] # 删除索引1的元素
print(languages) # 输出:['Python', 'C++', 'Python']
languages.remove("Python") # 删除第一个"Python"
print(languages) # 输出:['C++', 'Python']
popped = languages.pop() # 删除最后一个元素
print(popped) # 输出:Python
print(languages) # 输出:['C++']
常用操作
len(list):获取列表长度。list.count(x):统计元素x出现的次数。list.sort():对列表排序(原地修改)。list.reverse():反转列表(原地修改)。
nums = [3, 1, 4, 1, 5]
print(len(nums)) # 输出:5(长度)
print(nums.count(1)) # 输出:2(1出现的次数)
nums.sort() # 排序
print(nums) # 输出:[1, 1, 3, 4, 5]
nums.reverse() # 反转
print(nums) # 输出:[5, 4, 3, 1, 1]
元组(Tuple):不可变的序列
元组用小括号 (),跟列表最大的区别就是不可变——创建的时候啥样,一辈子就啥样。
基本定义与创建
# 普通元组
tup1 = (1, 2, 3, 4)
# 单元素元组(必须加逗号,否则会被视为普通数字)
single_tuple = (5,)
# 空元组
empty_tuple = ()
# 省略括号的元组(Python 允许)
implicit_tuple = 10, 20, 30
# 嵌套元组
nested_tuple = (1, (2, 3), (4, 5, 6))
访问元素
元组的访问方式和列表一样,支持索引和分片:
animals = ("cat", "dog", "bird", "fish")
print(animals[2]) # 输出:bird(索引2的元素)
print(animals[-2]) # 输出:bird(倒数第二个元素)
print(animals[1:3]) # 输出:('dog', 'bird')(分片)
不可变性说明
元组的元素一旦创建就无法修改,试图修改会报错:
tup = (1, 2, 3)
tup[0] = 10 # 报错:TypeError: 'tuple' object does not support item assignment
但有个例外:元组里套了可变对象,那个对象内部可以变
mutable_in_tuple = (1, [2, 3], 4)
mutable_in_tuple[1][0] = 20 # 元组中的列表元素可修改
print(mutable_in_tuple) # 输出:(1, [20, 3], 4)
这叫”元组不可变”指的是它里面装的东西的引用不可变,但引用指向的那个东西如果是可变的,它自己可以变。这个理解有点绕,记住”元组本身不能增删改元素”就够了。
元组的常用操作
虽然元组不可变,但支持以下操作:
len(tuple):获取长度。tuple.count(x):统计元素x出现的次数。tuple.index(x):返回元素x第一次出现的索引。- 元组拼接(创建新元组,原元组不变)。
t1 = (1, 2, 3)
t2 = (4, 5)
print(len(t1)) # 输出:3
print(t1.count(2)) # 输出:1
print(t1.index(3)) # 输出:2
t3 = t1 + t2 # 拼接元组(创建新元组)
print(t3) # 输出:(1, 2, 3, 4, 5)
函数返回多参数
严格来说,一个函数只能返回一个值,但是如果这个值是一个元组,效果就会和返回多个值一样了
def get_name_and_age():
name = "Alice"
age = 25
return name, age # 这会返回一个元组 (('Alice', 25),)
name, age = get_name_and_age()
print(name)
print(age)
列表与元组的核心区别
| 特性 | 列表(List) | 元组(Tuple) |
|---|---|---|
| 定义符号 | 方括号 [] |
小括号 ()(可省略) |
| 可变性 | 可变(可修改、添加、删除元素) | 不可变(元素创建后无法修改) |
| 性能 | 略低(需维护可变结构) | 更高(内存占用少,访问速度快) |
| 适用场景 | 元素需动态修改(如数据收集) | 元素固定不变(如配置、常量) |
| 哈希性 | 不可哈希(不能作为字典的键) | 可哈希(可作为字典的键) |
查看类型
如果不确定值是什么类型,可以使用type来进行判断
type(6)
<class 'int'>
type('H')
<class 'str'>
fruits = ["apple", "banana", "cherry", "date"]
type(fruits)
<class 'list'>
tup1 = (1, 2, 3, 4)
type(tup1)
<class 'tuple'>
几个容易踩的坑
坑1:列表作为函数默认参数
# 这样写有问题
def add_item(item, my_list=[]):
my_list.append(item)
return my_list
add_item(1) # [1]
add_item(2) # [1, 2] 意外!两次调用用的是同一个列表
默认参数在函数定义的时候就创建了,每次调用如果用同一个默认值,就会共用这个列表。正确写法:
def add_item(item, my_list=None):
if my_list is None:
my_list = []
my_list.append(item)
return my_list
坑2:浅拷贝 vs 深拷贝
nested = [[1, 2], [3, 4]]
copy = nested[:] # 浅拷贝
copy[0][0] = 99
print(nested) # [[99, 2], [3, 4]] 原列表也变了!
切片只复制了外层的引用,里面的列表还是同一个。想完全独立用 copy.deepcopy()。
坑3:列表乘法搞出来的意外
matrix = [[0] * 3] * 3 # 想建个3x3矩阵
matrix[0][0] = 1
print(matrix) # [[1, 0, 0], [1, 0, 0], [1, 0, 0]]
[[0] * 3] * 3 创建了三个指向同一个列表的引用,改一个全改。正确做法:
matrix = [[0] * 3 for _ in range(3)] # 列表推导式,每个都是独立的