PAYATHON 2026

Python 中如何在遍历 list 时移除包含特殊字符的元素?

技术老齐

可以用 str.isalnum() 判断字符串是否只含字母和数字,再通过列表推导式保留符合条件的元素。words 较大时,这种写法可以避免反复删除元素的开销,也能避免遍历时修改列表造成的漏检。

以下示例假设 words 中的元素都是 Python 3 字符串。

推荐写法:过滤后重新赋值

words = ["hello", "abc123", "hello!", "a_b", "two words", "中文123"]

words = [word for word in words if word.isalnum()]

print(words)
# ['hello', 'abc123', '中文123']

word.isalnum() 返回布尔值:

  • 字符串非空,且每个字符都是字母或数字时,返回 True。
  • 含有空格、标点、下划线等字符时,返回 False。
  • 空字符串 "" 也返回 False。

列表推导式会保留元素的原有顺序,也会保留重复元素。

如何判断“包含特殊字符”

对于非空字符串,可以直接对 word.isalnum() 的结果取反:

contains_special = not word.isalnum()

这个表达式对空字符串也会返回 True。如果“包含特殊字符”指的是“至少包含一个非字母数字字符”,可以逐字符判断:

def has_special_chars(word):
    return any(not char.isalnum() for char in word)


print(has_special_chars("abc123"))  # False
print(has_special_chars("abc!"))    # True
print(has_special_chars(""))        # False

any() 遇到第一个非字母数字字符就会返回 True,不再检查后面的字符。用它过滤列表,可以写成:

words = [word for word in words if not has_special_chars(word)]

这种写法会保留空字符串。如果想用 isalnum() 并保留空字符串,也可以这样写:

words = [word for word in words if not word or word.isalnum()]

为什么不直接在循环里调用 remove()

下面的代码可能漏掉需要删除的元素:

# 不推荐
for word in words:
    if not word.isalnum():
        words.remove(word)

删除元素后,后面的元素会向前移动,迭代却仍然向前推进。例如,遍历 ["a!", "b!", "ok"] 时,删除 "a!" 后,"b!" 会移到索引 0。下一次迭代读取的是索引 1 的 "ok",于是跳过了 "b!"。

list.remove() 还需要查找元素,并在删除后移动后续元素。即使改为遍历副本来避免漏检,反复删除的最坏时间开销仍与元素数量的平方相关。

需要修改原来的列表对象时

如果其他变量也引用了同一个列表,并且需要通过这些变量看到过滤后的内容,可以使用切片赋值:

words[:] = [word for word in words if word.isalnum()]

两种赋值方式的区别是:

  • words = [...]:创建新列表,让变量 words 指向它。
  • words[:] = [...]:替换原列表的全部内容,保留列表对象本身。

切片赋值的右侧仍会先创建一个临时列表,因此也需要额外内存。

大列表与字符范围的注意事项

列表推导式只遍历一次列表。设元素数量为 n,所有字符串的字符总数为 L,过滤的最坏时间复杂度为 O(n + L)。结果列表最多需要 O(n) 的额外元素引用空间,不会复制字符串内容。

使用前还需要确认允许的字符范围。Python 3 的 str.isalnum() 按 Unicode 判断,中文、带重音的字母以及部分非 ASCII 数字也可能通过检查。如果只允许英文字母 A-Z、a-z 和数字 0-9,可以写成:

words = [
    word
    for word in words
    if word.isascii() and word.isalnum()
]

这个版本同样会移除空字符串,也会移除含有空格、下划线或连字符的字符串。

备注:内容仅供参考。