06 - 列表与元组
06 - 列表与元组
列表大概是 Python 里用得最多的数据结构了。元组跟它很像,但有个关键区别。这章把两个放一起讲。
列表是什么
列表就是一个有序的容器,里面可以放任何东西,而且可以随时增删改。
fruits=["苹果","香蕉","橘子"]numbers=[1,2,3,4,5]mixed=[1,"hello",True,3.14]# 什么类型都能混着放empty=[]# 空列表用方括号[]创建,元素之间用逗号隔开。
访问元素
跟字符串一样,用索引和切片:
fruits=["苹果","香蕉","橘子","葡萄"]print(fruits[0])# 苹果print(fruits[-1])# 葡萄print(fruits[1:3])# ['香蕉', '橘子']print(fruits[::-1])# ['葡萄', '橘子', '香蕉', '苹果']列表长度用len():
print(len(fruits))# 4修改列表
字符串不能改,但列表可以。
改元素
fruits=["苹果","香蕉","橘子"]fruits[1]="芒果"print(fruits)# ['苹果', '芒果', '橘子']添加元素
fruits=["苹果","香蕉"]# append:在末尾加一个fruits.append("橘子")print(fruits)# ['苹果', '香蕉', '橘子']# insert:在指定位置插入fruits.insert(1,"芒果")print(fruits)# ['苹果', '芒果', '香蕉', '橘子']# extend:把另一个列表的元素加进来more=["葡萄","西瓜"]fruits.extend(more)print(fruits)# ['苹果', '芒果', '香蕉', '橘子', '葡萄', '西瓜']append和extend的区别,新手容易搞混:
a=[1,2,3]a.append([4,5])# [1, 2, 3, [4, 5]],把整个列表当成一个元素加进去a.extend([4,5])# [1, 2, 3, 4, 5],把里面的元素一个个加进去也可以用+运算符:
a=[1,2,3]b=[4,5,6]c=a+bprint(c)# [1, 2, 3, 4, 5, 6]删除元素
fruits=["苹果","香蕉","橘子","香蕉"]# remove:按值删除(只删第一个匹配的)fruits.remove("香蕉")print(fruits)# ['苹果', '橘子', '香蕉']# pop:按索引删除并返回(默认删最后一个)fruits=["苹果","香蕉","橘子"]last=fruits.pop()print(last)# 橘子print(fruits)# ['苹果', '香蕉']second=fruits.pop(1)# 删索引 1print(second)# 香蕉# del:也可以按索引删fruits=["苹果","香蕉","橘子"]delfruits[1]print(fruits)# ['苹果', '橘子']# clear:清空整个列表fruits.clear()print(fruits)# []remove如果找不到要删的值会报错,所以用的时候最好先确认一下在不在:
if"香蕉"infruits:fruits.remove("香蕉")列表的常用操作
排序
numbers=[3,1,4,1,5,9,2,6]# sort():原地排序(直接修改原列表)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]# sorted():不修改原列表,返回新的排序列表numbers=[3,1,4]new_list=sorted(numbers)print(numbers)# [3, 1, 4](没变)print(new_list)# [1, 3, 4]sort()和sorted()的区别:一个改原来的,一个创建新的。看你的需求选。
还可以自定义排序规则:
words=["banana","apple","cherry"]words.sort(key=len)# 按长度排序print(words)# ['apple', 'banana', 'cherry']查找
fruits=["苹果","香蕉","橘子","香蕉"]# index():找某个值的索引(只返回第一个)print(fruits.index("香蕉"))# 1# count():某个值出现了几次print(fruits.count("香蕉"))# 2# in:在不在里面print("苹果"infruits)# True反转
numbers=[1,2,3,4,5]numbers.reverse()print(numbers)# [5, 4, 3, 2, 1]复制列表
这里有个坑要注意:
a=[1,2,3]b=a# 这不是复制!b 和 a 指向同一个列表b.append(4)print(a)# [1, 2, 3, 4],a 也变了!正确复制的方式:
a=[1,2,3]b=a.copy()# 方法一:copy()b=a[:]# 方法二:切片b=list(a)# 方法三:list()b.append(4)print(a)# [1, 2, 3](没变)print(b)# [1, 2, 3, 4]但这些都是浅拷贝。如果列表里有嵌套的可变对象,里面的对象还是共享的:
a=[[1,2],[3,4]]b=a.copy()b[0].append(99)print(a)# [[1, 2, 99], [3, 4]],里面的列表还是被改了如果需要完全的深拷贝:
importcopy a=[[1,2],[3,4]]b=copy.deepcopy(a)b[0].append(99)print(a)# [[1, 2], [3, 4]],完全独立了列表推导式(预告)
这个后面第 17 章会详细讲,这里先见识一下:
# 生成 1-10 的平方squares=[x**2forxinrange(1,11)]print(squares)# [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]# 过滤偶数evens=[xforxinrange(1,11)ifx%2==0]print(evens)# [2, 4, 6, 8, 10]一行顶一个循环,Python 的招牌特性之一。
元组
元组跟列表几乎一样,区别就一个:元组不能改。
用圆括号()创建:
point=(3,4)colors=("红","绿","蓝")empty=()# 空元组single=(42,)# 只有一个元素的元组,注意逗号不能少那个逗号很重要。(42)不加逗号的话 Python 会以为你在写数学表达式,结果就是数字42,不是元组。
元组能做什么
索引和切片跟列表一样:
point=(3,4)print(point[0])# 3print(point[-1])# 4不能改:
point=(3,4)# point[0] = 5 # TypeError!那为什么要用元组?
我当初学的时候就在想,列表能改元组不能改,那直接用列表不就好了?
后来发现元组有几个不可替代的地方:
- 安全。你不想让别人改的数据就用元组,比如坐标、颜色值这些"一旦确定就不该变"的东西。
- 可以做字典的 key。列表不行(因为列表可变),元组可以。
- 解包。这个是元组最帅的用法:
# 解包x,y=(3,4)print(x,y)# 3 4# 函数返回多个值其实就是返回元组defget_position():return3,4# 等价于 return (3, 4)x,y=get_position()- 性能。元组比列表占内存少,创建速度也稍微快一点。差别不大,但在大数据量的时候有感知。
命名元组
如果你觉得(3, 4)这种写法不够直观,不知道每个位置是什么意思,可以用命名元组:
fromcollectionsimportnamedtuple Point=namedtuple("Point",["x","y"])p=Point(3,4)print(p.x,p.y)# 3 4看着像对象,但本质还是元组(不可变)。在定义简单的数据容器时很好用。
深拷贝与浅拷贝
前面简单提过这个坑,这里展开讲讲。Python 里复制对象有两种方式,区别很大。
浅拷贝(Shallow Copy)
浅拷贝创建一个新对象,但里面的元素还是引用原来的:
importcopy a=[[1,2],[3,4]]b=a.copy()# 浅拷贝# b = a[:] # 这也是浅拷贝# b = list(a) # 这也是浅拷贝# b = copy.copy(a) # 这还是浅拷贝b[0].append(99)print(a)# [[1, 2, 99], [3, 4]] ← 里面的列表还是被改了!为什么?因为浅拷贝只是复制了外层列表,里面的[1, 2]和[3, 4]这两个列表对象还是共享的。画个图就是:
a → [ list1, list2 ] ↑ ↑ b → [ list1, list2 ] (list1 和 list2 是同一个对象)深拷贝(Deep Copy)
深拷贝会递归地复制所有层,完全独立:
importcopy a=[[1,2],[3,4]]b=copy.deepcopy(a)# 深拷贝b[0].append(99)print(a)# [[1, 2], [3, 4]] ← 完全不受影响print(b)# [[1, 2, 99], [3, 4]]a → [ list1, list2 ] b → [ list3, list4 ] (全新的对象)什么时候用哪个?
| 场景 | 用什么 |
|---|---|
| 列表里都是不可变对象(数字、字符串) | 浅拷贝就够了 |
| 列表里有嵌套的可变对象(列表、字典) | 需要深拷贝 |
| 不确定 | 用深拷贝,保险 |
# 全是数字,浅拷贝没问题a=[1,2,3]b=a.copy()b.append(4)print(a)# [1, 2, 3] 不受影响# 有嵌套列表,浅拷贝会出问题a=[{"name":"小明"},{"name":"小红"}]b=a.copy()b[0]["name"]="小刚"print(a[0]["name"])# "小刚" ← 被改了!注意一个细节
如果嵌套的是不可变对象,浅拷贝也安全:
a=[(1,2),(3,4)]# 里面是元组(不可变)b=a.copy()# b[0] = (99, 99) # 这会创建新元组,不影响 a判断要不要深拷贝的核心问题:里面的东西能不能被原地修改?能的话就需要深拷贝。
本章小结
- 列表
[]是可变的有序容器,元组()是不可变的 - 列表常用操作:
append、insert、extend、remove、pop、sort - 复制列表要用
.copy()或[:],直接=是引用 - 浅拷贝只复制外层,嵌套的可变对象还是共享的;深拷贝(
copy.deepcopy)递归复制所有层 - 元组不可变,但可以做字典 key、解包赋值、性能更好
- 单元素元组要加逗号:
(42,)
面试题
Q1:列表和元组的区别是什么?什么时候用元组?
点击查看答案核心区别:列表可变,元组不可变。
用元组的场景:
- 数据不应该被修改时(如坐标、配置项)
- 需要做字典的 key 时(列表不行)
- 函数返回多个值时(实际上就是返回元组)
- 需要更好的性能和更少的内存占用时
元组因为不可变,在内存中占的空间更小,创建速度更快,也可以被哈希(hash)。
Q2:a = [1, 2, 3]; b = a; b.append(4),a 的值是什么?怎么避免这个问题?
a的值是[1, 2, 3, 4]。因为b = a只是让b和a指向同一个列表对象,不是创建副本。
避免的方法(创建浅拷贝):
b = a.copy()b = a[:]b = list(a)
如果列表里嵌套了可变对象(如列表里有列表),需要用copy.deepcopy(a)做深拷贝。
Q3:sort()和sorted()的区别?
sort()是列表的方法,原地排序,修改列表本身,返回Nonesorted()是内置函数,不修改原对象,返回新的排序列表
sorted()可以对任何可迭代对象排序(列表、元组、字符串等),sort()只能用于列表。
两者都支持reverse=True(降序)和key=函数(自定义排序规则)参数。
Q4:append和extend有什么区别?
append(x)把x当作一个元素添加到列表末尾extend(iterable)把可迭代对象中的每个元素分别添加到列表末尾
a=[1,2]a.append([3,4])# [1, 2, [3, 4]]a.extend([3,4])# [1, 2, 3, 4]等价写法:a.extend([3, 4])跟a += [3, 4]效果一样。
上一章 | 下一章:字典与集合 →
