前言
这是一篇「常用代码片段」合集:日常写 Python 时反复要写的那几件事——交换变量、去重、扁平化、统计词频、合并字典、按值排序、成对遍历——都有比「你自己手写循环」更短、更清楚的现成写法。这些写法几乎都来自标准库,不需要装任何东西。
但要先立一个前提:「一行写完」不等于「更好」。有些一行写法确实更清晰(比如a, b = b, a交换变量),有些一行写法只是把复杂度藏起来,过两周自己都看不懂。本文挑的都是既短又好读的,并且会标注哪些依赖较新的 Python 版本、哪些只是实现细节不能依赖。
还有一个常见误解要提前说清:很多「技巧」依赖具体版本或实现。比如字典从Python 3.7 起才在语言层面保证保留插入顺序(3.6 是 CPython 的实现细节,官方不建议依赖),「用dict.fromkeys去重还能保序」正是建立在这条保证上的。再比如整数缓存(CPython 缓存-5到256)属于实现细节,跟这些日常片段无关但同样别依赖。本文示例以 Python 3.8 为基线,用到更高版本特性时会写明版本。
一、变量与序列的小操作
# 适用于 Python 3.8+
# 交换两个变量,不需要临时变量
a, b = 1, 2
a, b = b, a
print(a, b) # 2 1
# 连续赋值与解包
x = y = 0
first, *rest = [1, 2, 3, 4]
print(first, rest) # 1 [2, 3, 4]
# 链式比较,写起来和数学一样
age = 25
print(0 <= age < 100) # True
# 条件表达式(三元运算)
label = "成年" if age >= 18 else "未成年"
print(label) # 成年解包里的*rest会把剩余元素收成一个列表,这是first, *rest = ...的固定语义。
二、去重与扁平化
保序去重是最常被写错的一个。用set去重会丢顺序,正确做法是用dict.fromkeys:
# 适用于 Python 3.7+(依赖字典保序)
seq = ["b", "a", "b", "c", "a"]
print(list(dict.fromkeys(seq))) # ['b', 'a', 'c']
# 反例:set 去重顺序不可控
print(list(set(seq))) # 顺序不保证,别依赖dict.fromkeys(seq)用seq的元素当键建字典,键天然唯一且(3.7+)保序,再取出来就是去重后的序列。
把嵌套列表压平(flatten)有两种常见写法:
# 适用于 Python 3.8+
matrix = [[1, 2, 3], [4, 5], [6]]
flat = [x for row in matrix for x in row]
print(flat) # [1, 2, 3, 4, 5, 6]
from itertools import chain
flat2 = list(chain.from_iterable(matrix))
print(flat2) # [1, 2, 3, 4, 5, 6]嵌套列表推导式的顺序是「先写外层 for,再写内层 for」,for row in matrix for x in row对应「先遍历行、再遍历行内元素」,写反了结果就不对。chain.from_iterable语义相同,但对任意层数的可迭代更通用。
反转序列注意原地与非原地的区别:
# 适用于 Python 3.8+
s = "abc"
print(s[::-1]) # 'cba' 返回新字符串
lst = [1, 2, 3]
print(list(reversed(lst))) # [3, 2, 1]
lst.reverse() # 原地反转,返回 None
print(lst) # [3, 2, 1]三、字符串常用处理
# 适用于 Python 3.8+
# 判断回文
def is_palindrome(text):
cleaned = "".join(ch.lower() for ch in text if ch.isalnum())
return cleaned == cleaned[::-1]
print(is_palindrome("A man, a plan, a canal: Panama")) # True
# 统计词频
from collections import Counter
words = "the quick brown fox jumps over the lazy dog the".split()
counts = Counter(words)
print(counts.most_common(2)) # [('the', 3), ('quick', 1)]
# 字符串反转与大小写
print("hello".capitalize()) # 'Hello'
print("hello world".title()) # 'Hello World'Counter是collections里的字典子类,专为计数设计;most_common(n)返回出现次数最多的前 n 项(按次数降序)。它的构造参数是任意可迭代对象。
四、字典常用处理
合并字典在 3.9 之前和之后写法不同:
# 适用于 Python 3.9+
d1 = {"a": 1, "b": 2}
d2 = {"b": 20, "c": 30}
print(d1 | d2) # {'a': 1, 'b': 20, 'c': 30} 右边覆盖左边
d1 |= d2 # 原地合并
print(d1) # {'a': 1, 'b': 20, 'c': 30}# 适用于 Python 3.8 及更早:用字典解包
merged = {**d1, **d2}安全取值用get而不是下标,下标取不存在的键会抛KeyError:
# 适用于 Python 3.8+
d = {"a": 1}
print(d.get("a")) # 1
print(d.get("z")) # None
print(d.get("z", 0)) # 0
print(d.setdefault("n", [])) # [] —— 键不存在时插入并返回默认值按值排序、找最大值的键、反转字典:
# 适用于 Python 3.8+
scores = {"alice": 88, "bob": 95, "carol": 72}
print(sorted(scores.items(), key=lambda kv: kv[1], reverse=True))
# [('bob', 95), ('alice', 88), ('carol', 72)]
print(max(scores, key=scores.get)) # bob
print({v: k for k, v in scores.items()}) # 反转(值必须唯一才有意义)sorted返回新列表;scores.items()是视图,本身不改动字典。
五、迭代与组合工具
# 适用于 Python 3.8+
# enumerate:同时拿到下标和值
for i, name in enumerate(["a", "b"], start=1):
print(i, name) # 1 a / 2 b
# zip:成对遍历,长度以短的为准
names = ["a", "b", "c"]
ages = [1, 2]
print(list(zip(names, ages))) # [('a', 1), ('b', 2)] 只到短的结束zip默认在最短的可迭代对象耗尽时停止。想按最长的来、缺的用占位值补,用itertools.zip_longest:
# 适用于 Python 3.8+
from itertools import zip_longest
print(list(zip_longest([1, 2, 3], ["a"], fillvalue="-")))
# [(1, 'a'), (2, '-'), (3, '-')]itertools.groupby用来按 key 分组,但它只对相邻的相同 key 分组,所以用之前必须先排序:
# 适用于 Python 3.8+
from itertools import groupby
data = [("fruit", "apple"), ("veg", "carrot"), ("fruit", "banana")]
data.sort(key=lambda p: p[0]) # 必须先排序
for key, group in groupby(data, key=lambda p: p[0]):
print(key, [g[1] for g in group])
# fruit ['apple', 'banana']
# veg ['carrot']六、读取与小结
# 适用于 Python 3.8+
# 一行读入两个整数
a, b = map(int, input().split())
# 生成器表达式省内存(不是列表)
total = sum(x * x for x in range(1000))
print(total)生成器表达式(x * x for x in ...)和列表推导式[x * x for x in ...]的区别是:前者惰性求值、不一次性建出整个列表,适合喂给sum、max、join这类会自己消费的函数。
常见坑点
- 用 set 做保序去重
❌list(set(data))顺序不可控,输出每次可能不同
✅list(dict.fromkeys(data))(3.7+ 保证保序)
- 嵌套推导式写反层序
❌[x for x in row for row in matrix]→NameError或结果错乱
✅[x for row in matrix for x in row],外层 for 写在前面
- 把 reverse() 的返回值接住
❌sorted_list = lst.reverse()得到None,lst反被原地改了
✅ 原地反用lst.reverse()且不接返回值;要新序列用reversed(lst)或lst[::-1]
- 下标取字典不存在的键
❌d["missing"]→KeyError
✅d.get("missing", 默认值),或在确需插入默认值时用setdefault
- 用 3.9 的
|合并字典却跑在 3.8
❌d1 | d2在 3.8 上对字典是TypeError(3.9 才支持)
✅ 3.9 以下写{**d1, **d2}
- groupby 之前忘了排序
❌ 直接groupby(data, key=...),相同的 key 分散在多处,被分成了好几组
✅ 先data.sort(key=...)再groupby,它只合并相邻项
- 以为 zip 会对齐到最长
❌zip([1, 2, 3], ["a"])期望得到三个元素,实际只有[(1, 'a')]
✅ 要补齐用itertools.zip_longest(..., fillvalue=...)
max(d, key=d.get)遇到并列时的迷惑
❌ 两个键的值一样大时,返回的是先遇到的那个,却以为有确定规则
✅ 并列时顺序取决于字典迭代顺序;需要确定的规则就自己写sorted后取第一个
总结
| 需求 | 片段 |
|---|
| 交换变量 | a, b = b, a |
| 保序去重 | list(dict.fromkeys(seq)) |
| 扁平化 | [x for row in m for x in row]或chain.from_iterable |
| 反转 | s[::-1]、reversed(x)(不原地) |
| 词频统计 | collections.Counter(it).most_common(n) |
| 合并字典 | 3.9+ 用 `d1 \ |
| 安全取值 | d.get(k, default) |
| 按下标遍历 | enumerate(it, start=1) |
| 成对遍历 | zip(a, b)(短的为准) |
| 相邻分组 | groupby(必须先排序) |
这些片段的价值不在于「短」,而在于它们背后都有清晰的语义:dict.fromkeys保序是字典保序保证的直接推论,zip短边截断、groupby只合相邻项是明确写进文档的行为。用之前先确认三件事——你的 Python 版本够不够、这段代码依赖的是语言保证还是实现细节、以及一行写法是不是真的比展开更好读。想清楚这三点,这些「常用功能」才会越用越顺,而不是变成下一个看不懂的历史代码。