Python基础指南之列表删除元素的六大常用方式详解
一、开篇:删除比增加复杂得多
上一篇文章我们学了增加元素的四种方式,今天进入更复杂的主题——删除元素。Python列表提供了惊人的六种删除方式:pop()、remove()、clear()、del语句、切片赋空、切片删除。
为什么删除比增加复杂?因为增加只关心"往哪加、加什么",而删除需要考虑"按什么删、删哪个、删完要不要返回值、删完列表怎么变"。选错删除方式,轻则代码可读性下降,重则产生难以排查的bug——比如在遍历时删除列表元素导致漏删或越界。
这篇文章我会把每种删除方式的适用场景、返回值、性能和避坑技巧都逐一讲清,同时重点讨论"边遍历边删除"这个经典难题的解决方案。
二、pop()——弹出并返回元素
2.1 基本用法
pop() 是唯一能同时完成删除和获取的方法——它删除指定位置的元素并返回它。
fruits = ['苹果', '香蕉', '橘子', '葡萄', '西瓜'] # pop():删除并返回最后一个元素(默认) last = fruits.pop() print(last) # 西瓜 print(fruits) # ['苹果', '香蕉', '橘子', '葡萄'] # pop(index):删除并返回指定索引的元素 second = fruits.pop(1) print(second) # 香蕉 print(fruits) # ['苹果', '橘子', '葡萄'] # pop(0):删除并返回第一个元素 first = fruits.pop(0) print(first) # 苹果 print(fruits) # ['橘子', '葡萄'] # pop负索引 fruits = ['苹果', '香蕉', '橘子', '葡萄'] second_last = fruits.pop(-2) print(second_last) # 橘子 print(fruits) # ['苹果', '香蕉', '葡萄']
2.2 pop() 的错误处理
# 对空列表pop会报IndexError
empty = []
try:
empty.pop()
except IndexError as e:
print(f'错误:{e}') # pop from empty list
# 索引越界也会报错
fruits = ['苹果', '香蕉']
try:
fruits.pop(5)
except IndexError as e:
print(f'错误:{e}') # pop index out of range
# 安全pop的写法
def safe_pop(lst, index=-1, default=None):
"""安全地弹出元素,出错时返回默认值"""
if not lst:
return default
try:
return lst.pop(index)
except IndexError:
return default
fruits = ['苹果', '香蕉']
print(safe_pop(fruits)) # 香蕉
print(safe_pop(fruits)) # 苹果
print(safe_pop(fruits)) # None(列表已空)
print(safe_pop([], 0, '空')) # '空'
2.3 pop() 实现栈和队列
# pop() 天然适合实现栈(后进先出 LIFO)
stack = []
stack.append('页面1')
stack.append('页面2')
stack.append('页面3')
# 后退操作
current = stack.pop() # 返回'页面3'
print(f'当前页面: {current}') # 当前页面: 页面3
current = stack.pop() # 返回'页面2'
print(f'返回后页面: {current}') # 返回后页面: 页面2
# pop(0) 可实现简单队列(先进先出 FIFO)
# 但效率低(O(n)),大量数据用collections.deque
queue = []
queue.append('任务1')
queue.append('任务2')
queue.append('任务3')
task = queue.pop(0) # 从头部取出
print(f'处理: {task}') # 处理: 任务1
虽然 pop(0) 能实现队列,但它的时间复杂度是 O(n)(后面的元素都要向前移动)。生产环境中大量数据用 collections.deque 的 popleft(),那是 O(1) 的。
三、remove()——按值删除
3.1 基本用法
remove(x) 删除列表中第一个值为 x 的元素。它不返回任何值(返回None)。
nums = [1, 3, 5, 3, 7, 3, 9]
# 删除第一个值为3的元素
nums.remove(3)
print(nums) # [1, 5, 3, 7, 3, 9](只删除第一个)
# 再删一次
nums.remove(3)
print(nums) # [1, 5, 7, 3, 9]
# 删除不存在的值——报ValueError
try:
nums.remove(100)
except ValueError as e:
print(f'错误:{e}') # list.remove(x): x not in list
3.2 remove() 的常见模式
# 模式一:安全删除(值可能不存在)
def safe_remove(lst, value):
"""安全删除,值不存在时不报错"""
try:
lst.remove(value)
return True
except ValueError:
return False
# 模式二:删除所有匹配的值
nums = [1, 3, 5, 3, 7, 3, 9, 3]
# 方式一:循环remove(简单但有性能顾虑)
while 3 in nums:
nums.remove(3)
print(nums) # [1, 5, 7, 9]
# 方式二:列表推导式(推荐)
nums = [1, 3, 5, 3, 7, 3, 9, 3]
nums = [x for x in nums if x != 3]
print(nums) # [1, 5, 7, 9]
# 模式三:删除满足条件的第一个元素
def remove_first_matching(lst, predicate):
"""删除第一个满足条件的元素"""
for i, item in enumerate(lst):
if predicate(item):
return lst.pop(i)
return None
scores = [85, 92, 78, 95, 88]
removed = remove_first_matching(scores, lambda x: x < 80)
print(f'删除了: {removed}') # 删除了: 78
print(f'剩余: {scores}') # 剩余: [85, 92, 95, 88]
3.3 remove() 的局限性
# remove() 只能按值删除,不能按条件删除
# 不支持的场景:
items = ['apple', 'Banana', 'cherry', 'APPLE']
# 想删除不区分大小写的'apple'
# items.remove('apple') # 只删除小写的
# items.remove('APPLE') # 还要再删一次
# 正确方式:用列表推导式
items = [x for x in items if x.lower() != 'apple']
print(items) # ['Banana', 'cherry']
# remove() 也不能删除满足某种条件的元素
nums = [1, 2, 3, 4, 5, 6, 7, 8, 9]
# 想删除所有偶数
# 不能写成 nums.remove(x % 2 == 0) # 这没有意义
# 正确方式:
nums = [x for x in nums if x % 2 != 0]
print(nums) # [1, 3, 5, 7, 9]
四、clear()——一键清空
4.1 基本用法
# clear() 清空列表中的所有元素 fruits = ['苹果', '香蕉', '橘子'] fruits.clear() print(fruits) # [] print(len(fruits)) # 0 # clear() 等价于 del lst[:] # 也等价于 lst[:] = [] fruits = ['苹果', '香蕉', '橘子'] del fruits[:] print(fruits) # [] fruits = ['苹果', '香蕉', '橘子'] fruits[:] = [] print(fruits) # [] # clear() vs 重新赋值为空列表 lst1 = [1, 2, 3] lst2 = lst1 # lst2和lst1指向同一个列表 lst1 = [] # lst1指向了一个新的空列表 print(lst1) # [] print(lst2) # [1, 2, 3](lst2仍然指向原来的列表!) lst1 = [1, 2, 3] lst2 = lst1 lst1.clear() # 清空原列表(所有引用它的变量都能看到变化) print(lst1) # [] print(lst2) # [](lst2也看到列表被清空了)
lst.clear() 和 lst = [] 的关键区别:clear() 清空原列表对象(所有引用都受影响),而 lst = [] 只是让变量指向新列表(其他引用不受影响)。
五、del 语句——按索引删除
5.1 基本用法
fruits = ['苹果', '香蕉', '橘子', '葡萄', '西瓜'] # del:删除指定索引的元素(无返回值) del fruits[1] print(fruits) # ['苹果', '橘子', '葡萄', '西瓜'] # del支持负索引 del fruits[-1] print(fruits) # ['苹果', '橘子', '葡萄'] # del一次删除多个元素(按切片删除) fruits = ['苹果', '香蕉', '橘子', '葡萄', '西瓜'] del fruits[1:3] print(fruits) # ['苹果', '葡萄', '西瓜'] # del删除带步长的切片 nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] del nums[::2] # 删除所有偶数索引位置的元素 print(nums) # [1, 3, 5, 7, 9] # del删除整个列表 del nums # print(nums) # NameError: name 'nums' is not defined
5.2 del vs pop() vs remove()
# 三者的对比
lst = [10, 20, 30, 40, 50]
# pop():返回被删除的元素,按索引删
lst1 = [10, 20, 30, 40, 50]
deleted = lst1.pop(2)
print(f'pop删除: {deleted}, 剩余: {lst1}') # pop删除: 30, 剩余: [10, 20, 40, 50]
# remove():不返回值,按值删(删除第一个匹配的)
lst2 = [10, 20, 30, 40, 50]
lst2.remove(30)
print(f'remove删除后: {lst2}') # remove删除后: [10, 20, 40, 50]
# del:不返回值,按索引删(更灵活但无返回值)
lst3 = [10, 20, 30, 40, 50]
del lst3[2]
print(f'del删除后: {lst3}') # del删除后: [10, 20, 40, 50]
# 选择指南:
# - 需要被删除的值 → pop()
# - 知道索引但不需要返回值 → del
# - 知道值但不知道位置 → remove()
# - 清空全部 → clear()
# - 删除一段 → del和切片
六、切片方式删除——最灵活
6.1 切片赋空(lst[a:b] = [])
nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] # 删除一段 nums[3:7] = [] print(nums) # [0, 1, 2, 7, 8, 9] # 删除前N个 nums[:3] = [] print(nums) # [7, 8, 9] # 删除后N个 nums[-2:] = [] print(nums) # [7] # 删除偶数索引(等价于del nums[::2]) nums = [0, 1, 2, 3, 4, 5, 6, 7] nums[::2] = [] # 注意!步长≠1时,要求赋的值和切片元素数相等 print(nums) # [1, 3, 5, 7] # 此时[::2]切片结果有4个元素,赋空列表[](0个元素),不匹配→报错? # 实际上:步长≠1时,赋值的元素数量必须和切片匹配 # 但赋空列表是个例外...等等,让我实测: # 实际上在Python中: nums = [0, 1, 2, 3, 4, 5, 6, 7] # nums[::2] = [] # ValueError: attempt to assign sequence of size 0 to extended slice of size 4 # 所以步长≠1时,必须用del del nums[::2] print(nums) # [1, 3, 5, 7]
6.2 切片删除的应用
# 删除前N个和后M个之外的所有元素(保留中间)
def keep_middle(lst, remove_front, remove_back):
lst[:remove_front] = []
lst[-remove_back:] = []
return lst
data = list(range(1, 21)) # 1到20
result = keep_middle(data, 5, 5)
print(result) # [6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
# 清空列表但保持同一个对象
lst1 = [1, 2, 3]
lst2 = lst1
lst1[:] = [] # 清空内容但不改变对象身份
print(lst1) # []
print(lst2) # [](lst2也看到了变化)
# = vs [:] = [] vs clear()
a = [1, 2, 3]
b = a
# a = []:a指向新列表,b不变
# a[:] = []:原地清空,b也变
# a.clear():原地清空,b也变
七、遍历时安全删除——经典难题的完整解决方案
7.1 为什么遍历时删除会出问题
# 这是Python中最经典的陷阱之一
nums = [1, 2, 3, 4, 5, 6, 7, 8, 9]
# 错误示范:用for循环遍历时删除
# for i in range(len(nums)):
# if nums[i] % 2 == 0:
# del nums[i] # 删除后索引会移位!后续元素会跳过或越界
# 具体说明:
items = ['a', 'b', 'c', 'd']
# 想删除'b'和'c'
# for i in range(len(items)):
# if items[i] in ('b', 'c'):
# del items[i]
# i=0: items[0]='a',不删
# i=1: items[1]='b',删除 → items变为['a','c','d']
# i=2: items[2]='d'('c'被跳过了!因为列表移位)
# 结果:'c'没有被检查到!
7.2 解决方案汇总
nums = [1, 2, 3, 4, 5, 6, 7, 8, 9]
target = [1, 2, 3, 4, 5, 6, 7, 8, 9]
# ✅ 方案一:创建新列表(最推荐,最清晰)
result1 = [x for x in target if x % 2 != 0]
print(f'方案一(推导式): {result1}')
# ✅ 方案二:从后往前遍历(避免索引移位的影响)
target = [1, 2, 3, 4, 5, 6, 7, 8, 9]
for i in range(len(target) - 1, -1, -1):
if target[i] % 2 == 0:
del target[i]
print(f'方案二(逆序遍历): {target}')
# ✅ 方案三:用while + 条件判断
target = [1, 2, 3, 4, 5, 6, 7, 8, 9]
i = 0
while i < len(target):
if target[i] % 2 == 0:
del target[i] # 注意:删除后i不增加
else:
i += 1
print(f'方案三(while循环): {target}')
# ✅ 方案四:先收集要删除的,再统一删除
target = [1, 2, 3, 4, 5, 6, 7, 8, 9]
to_delete = [i for i, x in enumerate(target) if x % 2 == 0]
for i in reversed(to_delete):
del target[i]
print(f'方案四(先收集后删除): {target}')
# ✅ 方案五:filter函数
target = [1, 2, 3, 4, 5, 6, 7, 8, 9]
result5 = list(filter(lambda x: x % 2 != 0, target))
print(f'方案五(filter): {result5}')
# ✅ 方案六:切片赋值(原地修改)
target = [1, 2, 3, 4, 5, 6, 7, 8, 9]
target[:] = [x for x in target if x % 2 != 0]
print(f'方案六(切片赋值): {target}')
7.3 方案选择指南
# 日常开发选择优先级:
# 首选:列表推导式创建新列表(最Pythonic)
# 适用于:大多数场景,代码最清晰
result = [x for x in original if condition(x)]
# 如果必须原地修改(其他变量也引用了这个列表):
# 用切片赋值
original[:] = [x for x in original if condition(x)]
# 如果列表非常大且条件很少触发删除:
# 用逆序遍历
for i in range(len(original) - 1, -1, -1):
if condition(original[i]):
del original[i]
# 如果列表中包含需要特殊处理的删除:
# 用while循环
i = 0
while i < len(original):
if condition(original[i]):
del original[i]
else:
i += 1
八、实战:用删除操作清理数据
8.1 清理列表中的无效数据
def clean_data(items):
"""清理列表中的无效数据(None、空字符串、0等)"""
return [x for x in items if x is not None and x != '' and x != 0]
raw_data = ['hello', None, '', 'world', 0, 'python', None, '', 42]
cleaned = clean_data(raw_data)
print(cleaned) # ['hello', 'world', 'python', 42]
def remove_outliers(scores, lower_bound, upper_bound):
"""移除超出范围的分数(离群值)"""
return [s for s in scores if lower_bound <= s <= upper_bound]
scores = [85, 200, 92, -10, 78, 95, 150, 88]
valid_scores = remove_outliers(scores, 0, 100)
print(valid_scores) # [85, 92, 78, 95, 88]
def deduplicate_keep_order(items):
"""去重且保持原始顺序"""
seen = set()
# 不能直接修改正在遍历的列表,但可以构建新列表
return [x for x in items if not (x in seen or seen.add(x))]
items = [1, 3, 2, 1, 5, 3, 2, 4, 1]
print(deduplicate_keep_order(items)) # [1, 3, 2, 5, 4]
8.2 栈式操作实战——撤销功能
class UndoManager:
"""用pop()实现撤销功能"""
def __init__(self):
self._history = []
self._redo_stack = []
def do(self, action):
"""执行一个操作"""
self._history.append(action)
self._redo_stack.clear() # 新操作后不能重做
print(f'执行: {action}')
def undo(self):
"""撤销上一个操作"""
if not self._history:
print('没有可撤销的操作')
return
action = self._history.pop()
self._redo_stack.append(action)
print(f'撤销: {action}')
def redo(self):
"""重做上一个撤销的操作"""
if not self._redo_stack:
print('没有可重做的操作')
return
action = self._redo_stack.pop()
self._history.append(action)
print(f'重做: {action}')
manager = UndoManager()
manager.do('输入"hello"') # 执行: 输入"hello"
manager.do('输入" world"') # 执行: 输入" world"
manager.do('删除" world"') # 执行: 删除" world"
manager.undo() # 撤销: 删除" world"
manager.undo() # 撤销: 输入" world"
manager.redo() # 重做: 输入" world"
8.3 处理嵌套列表中的删除
def deep_remove(lst, target):
"""递归删除嵌套列表中的所有匹配元素"""
result = []
for item in lst:
if isinstance(item, list):
# 递归处理嵌套列表
cleaned = deep_remove(item, target)
if cleaned: # 跳过空列表
result.append(cleaned)
elif item != target:
result.append(item)
return result
nested = [1, 2, [3, 2, [4, 2, 5], 2], 6, 2, [7, 2]]
cleaned = deep_remove(nested, 2)
print(cleaned) # [1, [3, [4, 5]], 6, [7]]
九、性能与陷阱
9.1 删除操作的性能对比
import time
n = 10000
# pop()——末尾弹出 O(1)
lst = list(range(n))
start = time.perf_counter()
while lst:
lst.pop()
t_pop_end = time.perf_counter() - start
print(f'pop()末尾: {t_pop_end:.4f}秒')
# pop(0)——头部弹出 O(n)
lst = list(range(n))
start = time.perf_counter()
while lst:
lst.pop(0)
t_pop_front = time.perf_counter() - start
print(f'pop(0)头部: {t_pop_front:.4f}秒')
print(f'头部比末尾慢 {t_pop_front / t_pop_end:.0f} 倍')
# remove()——按值删除 O(n)
lst = list(range(n))
start = time.perf_counter()
for i in range(n):
lst.remove(i)
t_remove = time.perf_counter() - start
print(f'remove(): {t_remove:.4f}秒')
# 列表推导式——O(n)
lst = list(range(n))
start = time.perf_counter()
lst = [x for x in lst if x < n // 2]
t_comp = time.perf_counter() - start
print(f'推导式: {t_comp:.4f}秒')
9.2 常见陷阱
# 陷阱一:remove()只删除第一个匹配
lst = [1, 1, 1, 1, 1]
lst.remove(1)
print(lst) # [1, 1, 1, 1](只删了一个!)
# 陷阱二:pop()不可对空列表操作
# [].pop() # IndexError
# 陷阱三:del删除变量和del删除元素的混淆
# del list_name # 删除整个变量
# del list_name[index] # 删除元素
# 陷阱四:在循环中修改列表
lst = [1, 2, 3, 4, 5]
for x in lst: # 遍历的是原始列表!
if x % 2 == 0:
lst.remove(x)
print(lst) # [1, 3, 5](看起来正确,但内部跳过了一些元素)
# 实际过程:
# 迭代1: x=1,不删
# 迭代2: x=2,删除 → lst变为[1,3,4,5]
# 迭代3: x=4 ← 跳过了3!
# 迭代4: x=5,不删
# 对于[2, 4, 1, 3, 5]这种排列,结果就错了:
lst = [2, 4, 1, 3, 5]
for x in lst:
if x % 2 == 0:
lst.remove(x)
print(lst) # [1, 3, 5] —— 这个例子恰好对了
# 但换成 [2, 2, 4, 1, 3, 5] 看看:
lst = [2, 2, 4, 1, 3, 5]
for x in lst:
if x % 2 == 0:
lst.remove(x)
print(lst) # [2, 1, 3, 5] —— 少删了一个2!
# 教训:永远不要在用for循环遍历列表时删除其中的元素
十、本篇小结
Python列表六大删除方式速查:
| 方式 | 按什么删 | 有无返回值 | 时间复杂度 | 最适用场景 |
|---|---|---|---|---|
pop() | 索引(默认最后) | 有 | O(1)末尾/O(n)头部 | 需要拿到被删元素 |
remove() | 值(第一个匹配) | 无 | O(n) | 知道值不知道位置 |
clear() | 全部 | 无 | O(1) | 一键清空 |
del lst[i] | 索引 | 无 | O(n) | 精确索引删除 |
del lst[a:b] | 切片范围 | 无 | O(n) | 删除一段 |
lst[:]=[...] | 替换全部 | 无 | O(n) | 原地过滤 |
核心要诀:
- 需要返回值 →
pop() - 知道值不知道位置 →
remove() - 精确按位置删 →
del lst[i] - 清空全部 →
clear() - 遍历时删除 → 用列表推导式创建新列表,逆序遍历,或while循环
删除是列表操作中最容易踩坑的领域——特别是"边遍历边删除"。记住黄金法则:不确定的时候,就用列表推导式创建新列表。下一篇我们进入列表的查找、排序与反转方法。
以上就是Python基础指南之列表删除元素的六大常用方式详解的详细内容,更多关于Python列表删除元素的资料请关注脚本之家其它相关文章!
相关文章
python根据时间生成mongodb的ObjectId的方法
这篇文章主要介绍了python根据时间生成mongodb的ObjectId的方法,涉及Python操作mongodb数据库的技巧,具有一定参考借鉴价值,需要的朋友可以参考下2015-03-03
解决Python运行文件出现out of memory框的问题
今天小编就为大家分享一篇解决Python运行文件出现out of memory框的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧2018-12-12


最新评论