专业编程基础技术教程

网站首页 > 基础教程 正文

Python 列表:从入门到高阶,避坑 + 性能优化全攻略

ccvgpt 2025-03-24 14:20:32 基础教程 15 ℃

一、对话引出列表难题

小白的困惑

小白(满脸抓狂):我写了a = [1, 2]和b = a,为什么改b[0]后a也变了?这完全不符合我的预期啊!

专家的解答

专家(推了推眼镜,耐心解释):在 Python 中,列表是可变对象。当你使用b = a这样的直接赋值时,实际上是进行了引用拷贝,a和b指向的是同一个列表对象。所以,对b的任何修改,都会直接反映在a上。若你想要一个独立的副本,必须使用copy()方法或者切片操作。比如b = a.copy()或者b = a[:],这样b就会拥有属于自己的独立数据,修改b不会影响到a 。

Python 列表:从入门到高阶,避坑 + 性能优化全攻略

二、列表基础三连击

(一)创建与索引

#  标准创建
nums = [1, 2, 3]
mix_list = [1, "文本", True]
#  迷惑操作
wrong_list = [1, 2, ] #  尾随逗号合法但危险!
empty_list = [] #  空列表要这样写
# 索引切片
print(nums[1]) # → 2
print(nums[-1]) # → 3
print(nums[1:]) # → [2, 3](左闭右开)
  1. 标准创建:可以创建包含单一类型元素的列表,如nums,也能创建包含不同类型元素的混合列表,像mix_list。
  2. 迷惑操作:虽然 Python 允许在列表元素后添加尾随逗号,但这在多数情况下没有实际意义,还可能引发代码阅读和维护的混淆,所以尽量避免。创建空列表直接使用[]即可。
  3. 索引切片:列表索引从 0 开始,nums[1]获取第二个元素;nums[-1]获取最后一个元素;切片操作nums[1:]生成一个从索引 1 开始到列表末尾的新列表,遵循左闭右开原则。

专家提醒:务必牢记列表索引从 0 开始,切片操作会生成新列表,这在实际编程中非常关键,能避免许多不必要的错误 。

(二)增删改查核心操作

# 添加元素
nums.append(4) # → [1,2,3,4]
nums.insert(1, 9) # → [1,9,2,3,4]
# 删除元素
nums.remove(9) # 删除第一个匹配值
del nums[0] # 删除索引位置元素
# 修改元素
nums[1] = 99 # → [2,99,3,4]
  1. 添加元素:append()方法在列表末尾添加一个元素;insert()方法可以在指定索引位置插入元素。
  2. 删除元素:remove()方法删除列表中第一个匹配的元素;del语句可以删除指定索引位置的元素。
  3. 修改元素:直接通过索引赋值即可修改列表中对应位置的元素。

您可以想象列表像一列火车车厢,添加元素如同在火车末尾挂接新的车厢(append),或者在中间某个位置插入车厢(insert);删除元素则类似拆卸车厢,remove是拆掉第一个符合条件的车厢,del是按位置拆掉指定车厢;修改元素就像是给某个车厢重新装修 。

三、常用函数火力全开

(一)合并与扩展

a = [1, 2]
b = [3, 4]
print(a + b) # → [1,2,3,4](新建列表)
a.extend(b) # → [1,2,3,4](原地扩展)
#  经典错误
a.append(b) # → [1,2,[3,4]](嵌套列表)
  1. +运算符合并:使用+运算符会创建一个新的列表,将两个列表的元素依次合并进去。
  2. extend()方法扩展:extend()方法则是在原列表的基础上进行扩展,直接修改原列表,不会创建新的列表对象。
  3. 经典错误:append()方法是将传入的参数作为一个整体添加到列表末尾,所以a.append(b)会导致b作为一个子列表被添加到a中,形成嵌套列表,这往往不是我们想要的结果。

(二)排序与反转

nums = [3,1,4,2]
nums.sort() # → [1,2,3,4](原地排序)
sorted_nums = sorted(nums, reverse=True) # → [4,3,2,1](新建列表)
nums.reverse() # → [2,4,1,3](原地反转)
  1. sort()方法排序:sort()方法会对列表进行原地排序,直接修改原列表,使其元素按升序排列。
  2. sorted()函数排序:sorted()函数会返回一个新的已排序的列表,原列表保持不变。通过设置reverse=True,可以实现降序排序。
  3. reverse()方法反转:reverse()方法将列表中的元素顺序进行原地反转。

(三)列表推导式

# 传统写法
squares = []
for x in range(5):
	squares.append(x**2)
# 推导式写法
squares = [x**2 for x in range(5)] # → [0,1,4,9,16]
# 带条件筛选
even_squares = [x**2 for x in range(10) if x%2==0]
  1. 传统写法:通过循环遍历,依次计算每个数的平方并添加到列表中。
  2. 推导式写法:列表推导式提供了一种简洁的方式来创建列表。[x**2 for x in range(5)]这一行代码就实现了与传统循环相同的功能,代码更加简洁明了。
  3. 带条件筛选:在推导式中还可以添加条件筛选,[x**2 for x in range(10) if x%2==0]表示只计算 10 以内偶数的平方,并生成列表。

四、高阶函数工具箱

(一)元素定位三剑客

nums = [5, 2, 5, 8, 5]
# 查找元素位置
print(nums.index(5)) # → 0(首个匹配索引)
print(nums.index(5, 1)) # → 2(从索引1开始找)
# 元素计数
print(nums.count(5)) # → 3(出现次数)
# 弹出元素
last = nums.pop() # → 5(默认弹出最后一个)
second = nums.pop(1) # → 2(弹出指定索引)
  1. index()方法查找位置:index()方法用于查找元素在列表中的索引位置,若找到则返回首个匹配的索引;还可以指定从某个索引位置开始查找。
  2. count()方法计数:count()方法统计元素在列表中出现的次数。
  3. pop()方法弹出元素:pop()方法默认弹出并返回列表的最后一个元素;也可以传入索引,弹出指定位置的元素。

(二)列表堆栈与队列

# 栈操作(后进先出)
stack = []
stack.append(1) # 入栈
stack.append(2)
print(stack.pop()) # → 2 出栈
# 队列操作(先进先出)
from collections import deque
queue = deque(["A", "B"])
queue.append("C") # 入队
print(queue.popleft()) # → A 出队
  1. 栈操作:利用列表的append()和pop()方法,可以模拟栈的后进先出(LIFO)操作。append()实现入栈,pop()实现出栈。
  2. 队列操作:Python 的collections模块中的deque类可用于实现队列。append()方法用于入队,popleft()方法用于出队,遵循先进先出(FIFO)原则。

五、扩展知识:暗黑技巧

(一)列表解包魔法

# 基本解包
a, b, c = [1, 2, 3] # → a=1, b=2, c=3
# 星号解包
first, *middle, last = [1, 2, 3, 4, 5]
print(middle) # → [2, 3, 4]
# 合并列表
combined = [*[1,2], *[3,4]] # → [1,2,3,4]
  1. 基本解包:可以将列表中的元素按顺序依次赋值给多个变量,前提是变量数量与列表元素数量一致。
  2. 星号解包:使用星号(*)可以将列表中的部分元素收集到一个新的列表中。如first, *middle, last = [1, 2, 3, 4, 5],middle会收集中间的元素[2, 3, 4]。
  3. 合并列表:通过在列表前加星号,可以将多个列表合并成一个新列表,这种方式简洁高效。

(二)列表与生成器的完美结合

# 生成器表达式(处理海量数据)
big_data = (x**2 for x in range(1000000))
sum_result = sum(x for x in big_data if x%10==0)
# 内存对比测试(生成器省内存!)
import sys
print(sys.getsizeof([x for x in range(1000000)])) # → 8448728字节
print(sys.getsizeof(x for x in range(1000000))) # → 208字节
  1. 生成器表达式:生成器表达式类似于列表推导式,但它不会立即生成一个完整的列表,而是在需要时逐个生成元素,非常适合处理海量数据,能有效节省内存。
  2. 内存对比测试:通过sys.getsizeof()函数可以看到,生成器表达式占用的内存远远小于列表推导式生成的列表,在处理大数据集时优势明显。

(三)列表排序黑科技

students = [
{"name": "张三", "age": 20},
{"name": "李四", "age": 18}
]
# 单级排序
students.sort(key=lambda x: x["age"])
# 多级排序(先年龄后姓名)
students.sort(key=lambda x: (x["age"], x["name"]))
# 逆序+自定义排序
words = ["apple", "Banana", "cherry"]
words.sort(key=lambda x: x.lower())) # 忽略大小写排序
  1. 单级排序:对于包含字典的列表,可以使用sort()方法,并通过key参数指定一个函数来确定排序依据。如students.sort(key=lambda x: x["age"]),按学生年龄进行排序。
  2. 多级排序:可以在key函数中返回一个元组,实现多级排序。students.sort(key=lambda x: (x["age"], x["name"]))先按年龄排序,年龄相同再按姓名排序。
  3. 逆序 + 自定义排序:通过key函数结合lower()方法,可以实现忽略大小写的排序,同时还能通过设置reverse=True实现逆序。

把数据想象成一副扑克牌,传统排序像是简单地把牌按花色或数字顺序整理;而这里的排序黑科技就像是可以根据多种规则,如先按花色、再按数字,甚至可以特殊处理某些牌(忽略大小写等),对扑克牌进行花式排序 。

六、性能优化秘籍

(一)预分配空间加速

# 低效写法(动态扩容)
result = []
for i in range(10000):
	result.append(i)

# 高效写法(预分配)
result = [0] * 10000
for i in range(10000):
	result[i] = i
  1. 低效写法:在循环中不断使用append()方法向空列表中添加元素,列表会频繁进行动态扩容,这会消耗较多时间和内存。
  2. 高效写法:通过预分配空间,创建一个指定长度的列表(这里用[0] * 10000创建了一个长度为 10000 的列表),然后再按索引赋值,能显著提高效率。

(二)避免循环内重复操作

# 低效写法(每次循环都调用len())
for i in range(len(my_list)):
	if my_list[i] == target:
	...
# 高效写法(缓存长度)
list_len = len(my_list)
for i in range(list_len):
	if my_list[i] == target:
	...
  1. 低效写法:在循环条件中每次都调用len()函数获取列表长度,这是不必要的重复操作,会降低性能。
  2. 高效写法:先将列表长度缓存到一个变量中,然后在循环中使用该变量,避免了重复计算。

(三)numpy 混合使用(处理数值计算)

import numpy as np
# 列表转numpy数组
py_list = [1, 2, 3]
np_arr = np.array(py_list)
# 向量化运算(比循环快100倍!)
result = np_arr * 2 + 5
  1. 列表转 numpy 数组:numpy库的array()函数可以将 Python 列表转换为numpy数组。
  2. 向量化运算:numpy数组支持向量化运算,如np_arr * 2 + 5,这种运算比使用循环逐个处理元素快很多,在处理数值计算时能大幅提升性能。

七、闭坑指南(血泪总结)

浅拷贝陷阱

a = [[1,2], [3,4]]
b = a.copy() # 浅拷贝
b[0][0] = 99 # a也变成[[99,2], [3,4]]
# 正确解法:
import copy
c = copy.deepcopy(a)

对于嵌套列表,使用copy()方法进行的是浅拷贝,只会复制外层列表,内层列表仍然是引用。所以修改b的内层列表元素,a也会跟着改变。正确做法是使用copy.deepcopy()进行深拷贝,这样能确保c拥有完全独立的数据。

循环中删除元素

nums = [1,2,3,4]
for x in nums:
if x%2 ==0:
nums.remove(x) # → [1,3]  漏删!
# 正确解法:
nums = [x for x in nums if x%2!=0]

在循环中直接删除列表元素会导致索引混乱,从而出现漏删的情况。如上述代码,nums中偶数4就会被漏删。推荐使用列表推导式来筛选需要保留的元素,创建一个新的列表。

默认参数可变对象

def add_item(item, lst=[]):
  lst.append(item)
  return lst
print(add_item(1)) # → [1]
print(add_item(2)) # → [1,2]  累积结果!
# 正确解法:
def add_item(item, lst=None):
  lst = lst or []
  lst.append(item)
  return lst

在函数定义中,默认参数lst=[]是一个可变对象。这意味着每次调用函数时,如果不传入新的列表,都会使用同一个默认列表,导致结果累积。正确做法是将默认参数设为 None

小白:(跪了)原来列表还能这么玩!我现在就去重构代码!

专家:(露出欣慰笑容)记住:列表虽好,但数据量大时该用字典或数组!

最近发表
标签列表