变量赋值与多重赋值
x = 42 name = "Lei" a, b, c = 1, 2, 3 x, y = y, x # swap, no temp variable
Python 是动态类型,赋值就是把名字绑到对象上。一行多赋值很常见;交换两个变量用元组拆包,不用临时变量。
a = b = c = 0 # chained, all point to same object
first, *rest = [1, 2, 3, 4] # 1, [2,3,4]
Python 速查表,100+ 段地道 Python 代码片段,涵盖字符串/列表/字典/文件/异步,带真实例子。
x = 42 name = "Lei" a, b, c = 1, 2, 3 x, y = y, x # swap, no temp variable
Python 是动态类型,赋值就是把名字绑到对象上。一行多赋值很常见;交换两个变量用元组拆包,不用临时变量。
a = b = c = 0 # chained, all point to same object
first, *rest = [1, 2, 3, 4] # 1, [2,3,4]
n = 10 # int, 任意精度
pi = 3.14 # float, IEEE 754 双精度
z = 2 + 3j # complex
from decimal import Decimal
price = Decimal("0.1") + Decimal("0.2") # 精确 0.3int 任意精度(3 ** 200 也能算)。float 是 IEEE 754,0.1 + 0.2 不等于 0.3。算钱必用 Decimal,且参数要传字符串。
0.1 + 0.2 == 0.3 # False!
from fractions import Fraction; Fraction(1, 3)
bool(0) # False
bool("") # False
bool([]) # False
bool(None) # False
bool("False") # True (非空字符串都是 True)
bool([False]) # True (列表非空就是 True)假值只有这几个:0、0.0、""、[]、{}、set()、None、False。其他都是真值,字符串 "False" 是真,列表 [False] 也是真。
if items: # 比 if len(items) > 0 更地道
x = a or "default" # a 为假值时取 default
x = None
if x is None: # ✅ 推荐
print("空值")
if x == None: # 能跑但不地道
passNone 是单例,必须用 is None / is not None 判断,不要写 == None。is 比较对象身份,== 是值相等,可被重载坑掉你。
isinstance(x, int) # True if x is int (or bool!) isinstance(x, (int, float)) # 接受多种类型 type(x) is int # 严格相等,不认子类
用 isinstance 而不是 type() ==,isinstance 认继承。坑:bool 是 int 的子类,isinstance(True, int) 是 True。
# 排除 bool:isinstance(x, int) and not isinstance(x, bool)
print("a", "b", "c") # 默认空格分隔
print("a", "b", sep="-") # "a-b"
print("loading", end="") # 不换行
print("err msg", file=sys.stderr) # 打到 stderr
name = input("name: ") # 读一行(返回 str,不含换行)
age = int(input("age: ")) # 数字记得转print 接受 sep= 和 end=。input() 返回字符串(不含换行),要数字得手动 int() / float()。
range(5) # 0, 1, 2, 3, 4 range(2, 7) # 2, 3, 4, 5, 6 range(0, 10, 2) # 0, 2, 4, 6, 8 (步长) range(10, 0, -1) # 10, 9, 8, ..., 1 (反向) list(range(5)) # [0, 1, 2, 3, 4]
range(stop) / range(start, stop) / range(start, stop, step)。惰性的,迭代时才出数。stop 不包含。
5 & 3 # 1 按位与 5 | 3 # 7 按位或 5 ^ 3 # 6 按位异或 ~5 # -6 按位取反 (-(x+1)) 1 << 4 # 16 左移 (= 1 * 2**4) 256 >> 2 # 64 右移 (0b1010).bit_count() # 2 (3.10+ 数二进制里有几个 1)
位运算作用于 int。~x 等于 -(x+1)。x << n 就是 x * 2**n。3.10+ 加了 int.bit_count()(数 1 的个数)和 int.bit_length()。
(255).bit_length() # 8
int("ff", 16) # 255 按进制解析bin(10), hex(255), oct(8)
bin(10) # '0b1010'
hex(255) # '0xff'
oct(8) # '0o10'
int("1010", 2) # 10 按二进制解析
int("ff", 16) # 255
f"{255:#x}" # '0xff' 带前缀
f"{10:08b}" # '00001010' 补零到 8 位
1_000_000 # 下划线分隔,提高可读性bin/hex/oct 返回带前缀字符串;int(s, base) 反向解析。f-string 格式串(#x、08b)做格式化输出。字面量里的下划线只是分隔符,会被忽略。
round(3.14159, 2) # 3.14 round(2.5) # 2 (银行家舍入:四舍六入五取偶) round(3.5) # 4 abs(-7) # 7 pow(2, 10) # 1024 pow(2, 10, 1000) # 24 (2**10 % 1000,模幂,密码学常用) divmod(17, 5) # (3, 2) 商和余数
round() 用银行家舍入(四舍六入五取偶):round(2.5) 是 2 不是 3。pow(b, e, m) 是快速模幂,密码学常用。divmod 同时给商和余。
ord("A") # 65
chr(65) # 'A'
ord("中") # 20013
chr(0x1F600) # '😀'
"A" < "B" # True 按码点比较
[chr(c) for c in range(97, 123)] # a-zord() 拿字符的 Unicode 码点,chr() 反向。字符串比较按码点。chr(0x1F600) 用十六进制码点拼出 emoji。
name = "Lei"
age = 30
s = f"{name} is {age}" # "Lei is 30"
pi = 3.14159
s = f"{pi:.2f}" # "3.14"
n = 1234567
s = f"{n:,}" # "1,234,567"
s = f"{name=}, {age=}" # debug: "name='Lei', age=30"f-string(3.6+)是最快、最易读的格式化方式。:.2f 保留小数,:, 加千位分隔符,{var=} 自带变量名打印,调试神器。
f"{x:>10}" # 右对齐宽 10f"{x:0>5}" # 左补 0 到 5 位f"{x:.0%}" # 百分比"Hello, {}!".format("Lei")
"{0} {1} {0}".format("a", "b") # "a b a"
"{name} is {age}".format(name="Lei", age=30)
"Hello, %s! You are %d." % ("Lei", 30).format() 和 % 还能用,老项目里也常见。新代码推荐 f-string。
"a,b,c".split(",") # ['a', 'b', 'c']
"a b c".split() # ['a', 'b', 'c'] (默认按空白切,连续空白当一个)
",".join(["a", "b", "c"]) # "a,b,c"
"\n".join(lines) # 拼成多行字符串split() 不传参数按任意空白切并合并连续空白。join() 是字符串方法(分隔符在左),不是列表方法,这是新手第一个坑。
"a,b,,c".split(",") # ["a", "b", "", "c"] (空段保留)"a,b,c".split(",", 1) # ["a", "b,c"] (限制 split 次数)" hello ".strip() # "hello"
" hello ".lstrip() # "hello "
" hello ".rstrip() # " hello"
"xxhelloxx".strip("x") # "hello"
"https://example.com/docs/".rstrip("/") # "https://example.com/docs"strip() 默认去空白;传字符串是去那个字符集里的任意字符。坑:rstrip("ing") 是去 i/n/g 任意字符,不是去尾部的 "ing" 子串。
# Python 3.9+ 才有去前后缀 substring 的方法:
"running".removesuffix("ing") # "runn""running".removeprefix("ru") # "nning""hello world".replace("world", "Lei") # "hello Lei"
"a-b-c-d".replace("-", "_", 2) # "a_b_c-d" (限制次数)
import re
re.sub(r"\d+", "#", "abc 123 def 456") # "abc # def #" (正则替换)str.replace() 是普通子串替换,要正则用 re.sub()。replace() 返回新字符串,字符串不可变。
url.startswith("https://")
filename.endswith((".jpg", ".png", ".gif")) # 元组:满足任一即可
"lei" in "hello lei" # 子串包含
"lei" in ["lei", "han"] # 列表 in O(n)
"lei" in {"lei", "han"} # 集合 in O(1)startswith / endswith 接受元组,多扩展名一行搞定。in 在 list/string 上是 O(n),set/dict 上是 O(1),性能差距巨大。
"hello".upper() # "HELLO" "HELLO".lower() # "hello" "hello world".title() # "Hello World" "Hello".casefold() # "hello" (比 lower 更激进,能正确处理德语 ß) " Hello ".swapcase() # " hELLO "
lower() 处理 ASCII。casefold() 是大小写无关比较的正式做法,能正确处理德语 ß → ss。
s = "hello" s[0] # 'h' s[-1] # 'o' s[1:4] # "ell" s[:3] # "hel" s[::-1] # "olleh" (反转,最骚的一行) s[::2] # "hlo" (步长 2)
切片是 [start:stop:step],stop 不包含。负数从末尾数。s[::-1] 是反转字符串的骚操作,一行搞定。
"你好".encode("utf-8") # b'\xe4\xbd\xa0\xe5\xa5\xbd'
b'\xe4\xbd\xa0\xe5\xa5\xbd'.decode("utf-8") # "你好"
"abc".encode() # 默认 UTF-8
# 容错:errors="ignore" / "replace" / "strict"(默认)
b'\xff\xfe'.decode("utf-8", errors="replace") # "��"str → bytes 用 .encode(),bytes → str 用 .decode(),默认 UTF-8。errors="replace" / "ignore" 让坏字节不抛异常。
s = """第一行
第二行
第三行"""
import textwrap
textwrap.dedent("""
缩进的多行字符串
自动去掉前导空格
""")
textwrap.fill("超长一段话", width=40)三引号字符串保留换行。textwrap.dedent 去掉每行共同的前导空白,内嵌 SQL/模板特别好用。
s = "hello world hello"
s.find("hello") # 0 找不到返回 -1
s.find("xyz") # -1
s.rfind("hello") # 12 从右找
s.index("world") # 6 找不到抛 ValueError
s.count("hello") # 2 出现次数
s.count("l") # 5find() 返回索引,找不到给 -1;index() 找不到直接抛 ValueError。rfind 从右往左找。count() 数不重叠的出现次数。
"5".zfill(3) # "005" 左补零
"7".rjust(5) # " 7" 右对齐
"7".rjust(5, "0") # "00007"
"hi".ljust(5, ".") # "hi..."
"hi".center(7, "*") # "**hi***"
# f-string 等价写法:
f"{5:03d}" # "005"
f"{'hi':^7}" # " hi "zfill 左补零(能正确处理符号)。ljust/rjust/center 用填充字符补到指定宽度。f-string 格式串(:03d、:^7)等价行内写法。
"a\nb\r\nc".splitlines() # ['a', 'b', 'c'] 跨平台换行都认
"a\nb\n".split("\n") # ['a', 'b', ''] 末尾多个空段
"key=value".partition("=") # ('key', '=', 'value')
"no-sep".partition("=") # ('no-sep', '', '') 找不到也不抛错
"a=b=c".partition("=") # ('a', '=', 'b=c') 只切第一个splitlines 统一处理 \n / \r\n / \r(split 做不到)。partition 按第一个分隔切成三元组,找不到也不抛错,返回空段。
"123".isdigit() # True "12.3".isdigit() # False (点不是数字) "abc".isalpha() # True "abc123".isalnum() # True " ".isspace() # True "Hello".istitle() # True "ABC".isupper() # True "²".isdigit() # True! 上标也算 (用 isdecimal 更严)
is* 系列判断整串。坑:isdigit() 把上标、分数也算数字;要严格 0-9 用 isdecimal(),最宽用 isnumeric()。
table = str.maketrans("aeiou", "12345")
"hello".translate(table) # "h2ll4"
# 删除字符:
str.maketrans("", "", "aeiou")
"hello world".translate(str.maketrans("", "", "aeiou")) # "hll wrld"
# 全角转半角等批量替换比多次 replace 快maketrans 造映射表,translate 一遍替换。三参形式 (from, to, delete) 还能删字符。比连续 replace() 快。
import re
m = re.search(r"(\d+)-(\d+)", "id 12-34 end")
m.group(0) # "12-34"
m.group(1), m.group(2) # "12", "34"
re.match(r"\d+", "abc") # None (match 只从开头匹配)
re.findall(r"\d+", "a1b22c333") # ['1', '22', '333']
for m in re.finditer(r"\d+", text): # 拿到 Match 对象,含位置
print(m.group(), m.span())
# 预编译复用:
pat = re.compile(r"\w+@\w+\.\w+")search 全串扫,match 只从开头匹配。findall 返回字符串列表,finditer 返回带 .span() 的 Match 对象。循环里复用就先 re.compile。
re.split(r"\s+", text) # 按正则切
re.sub(r"\d", "#", s) # 正则替换
b = b"hello"
b.hex() # '68656c6c6f'
bytes.fromhex("68656c6c6f") # b'hello'
import base64
base64.b64encode(b"hi") # b'aGk='
base64.b64decode(b"aGk=") # b'hi'
# URL 安全变体:
base64.urlsafe_b64encode(data)bytes.hex() / fromhex() 在十六进制字符串间互转。base64 编解码作用于 bytes 不是 str。要塞进 URL 的 token 用 urlsafe_b64encode。
xs = [1, 2, 3] xs.append(4) # [1, 2, 3, 4] 单个元素 xs.extend([5, 6]) # [1, 2, 3, 4, 5, 6] 合并另一个可迭代 xs += [7, 8] # 等价于 extend xs.insert(0, 0) # [0, 1, 2, 3, ...] 指定位置插 xs.append([9, 10]) # [..., [9, 10]] 坑:嵌套了一层
append 加一个元素。extend(或 +=)合并一个可迭代。经典 bug:append([9, 10]) 是把整个列表当一个元素塞进去,套了一层。
xs = [1, 2, 3, 4, 5] xs[1:4] # [2, 3, 4] xs[:] # [1, 2, 3, 4, 5] 浅拷贝 xs[::-1] # [5, 4, 3, 2, 1] 反转 xs[1:3] = [20, 30] # 切片赋值:原地修改 xs[::2] = [10, 30, 50] # 步长切片赋值,两边长度要一致
切片语法和字符串一样。xs[:] 是浅拷贝的经典写法。切片赋值是原地修改,批量替换很方便。
squares = [x * x for x in range(10)] evens = [x for x in nums if x % 2 == 0] matrix = [[i * j for j in range(5)] for i in range(5)] # 双 for 拍平嵌套: flat = [x for row in matrix for x in row] # 带 else 的写法(注意 if 位置变了): labels = ["even" if x % 2 == 0 else "odd" for x in nums]
list 推导地道又快,一般比 for + append 还快。过滤 if 写在末尾;三元 if/else 必须写在 for 前面,位置不一样,新手常搞混。
# 三个以上推导嵌套就拆 for 循环吧,可读性优先
list(map(str, [1, 2, 3])) # ["1", "2", "3"] list(map(lambda x: x * 2, nums)) # 不推荐,写成推导更地道 list(filter(lambda x: x > 0, nums)) # 不推荐,写成 [x for x in nums if x > 0] # 多个可迭代一起: list(map(lambda a, b: a + b, [1, 2], [10, 20])) # [11, 22]
map 和 filter 返回的是迭代器,要 list() 包一下才能看。一次性用 lambda 的场景,写 list 推导更地道。
sorted([3, 1, 2]) # [1, 2, 3] 返回新列表 xs.sort() # 原地排序,返回 None sorted(words, key=len) # 按长度 sorted(users, key=lambda u: u.age) # 按属性 from operator import itemgetter, attrgetter sorted(rows, key=itemgetter(2)) # 比 lambda 快一点 sorted(xs, reverse=True) # 降序 # 多列排序:稳定排序,从次要到主要倒着 sort sorted(rows, key=lambda r: (r[0], -r[1])) # 第 1 列升、第 2 列降
sorted() 返回新列表,.sort() 原地排序且返回 None,不要写 xs = xs.sort(),那是个 None。key=itemgetter 比 lambda 快一点。Python sort 是稳定排序。
xs.reverse() # 原地反转,返回 None list(reversed(xs)) # 返回新列表 xs[::-1] # 也是反转,最简洁
.reverse() 原地修改;reversed() 返回迭代器(要 list() 包);xs[::-1] 返回新列表,最短。
# 不在乎顺序: list(set(xs)) # 保留首次出现顺序(3.7+ dict 有序): list(dict.fromkeys(xs)) # 复杂对象去重(按某 key): seen = set() uniq = [x for x in xs if not (x in seen or seen.add(x))]
set() 去重丢顺序。dict.fromkeys() 去重且保留首次出现顺序(3.7 起 dict 有序,可放心用)。
for i, v in enumerate(items):
print(i, v)
for i, v in enumerate(items, start=1): # 从 1 开始
print(i, v)
# 反例(不要写):
for i in range(len(items)):
print(i, items[i]) # 不地道enumerate(seq) 给你 (index, value) 对,永远不要写 for i in range(len(seq))。start= 改起始索引。
names = ["Lei", "Han", "Mei"]
ages = [30, 25, 28]
for n, a in zip(names, ages):
print(n, a)
# 转字典:
d = dict(zip(names, ages))
# 转置矩阵:
list(zip(*matrix))
# 3.10+ strict 模式,长度不一致就抛错:
zip(names, ages, strict=True)zip 把多个可迭代两两配对,默认按最短的截断。zip(*matrix) 转置矩阵。3.10+ strict=True 长度不一致直接抛错,避免静默截断。
all([True, True, True]) # True
all([]) # True (空集合默认 True,反直觉)
any([False, False, True]) # True
any([]) # False
# 实战:
all(x > 0 for x in nums) # 全部为正
any(s.startswith("error") for s in logs)all() 全为真才 True(空集合按空真规则返回 True,反直觉)。any() 至少一个为真就 True。传生成器表达式可短路提前结束。
sum([1, 2, 3]) # 6 sum([1, 2, 3], 100) # 106 (起始值) max([1, 2, 3]) min(["apple", "banana"], key=len) # "apple" max(users, key=lambda u: u.age) # 3.4+ 提供默认值: min([], default=0) # 不抛错
min/max 接受 key= 函数,找最长字符串/最年轻用户特别方便。空可迭代会抛 ValueError,传 default= 不抛。
xs = [10, 20, 30, 40] xs.pop() # 删最后一个,返回 40,xs = [10, 20, 30] xs.pop(0) # 删第 0 个,返回 10,xs = [20, 30] xs.remove(30) # 按值删第一个匹配的 del xs[0] # 按索引删(不返回值) del xs[1:3] # 按切片删
pop 返回被删的元素。remove 按值删第一个匹配(找不到抛 ValueError)。del 是语句不是表达式,没返回值。
xs = [10, 20, 30, 20]
xs.index(20) # 1 第一个匹配的下标
xs.index(20, 2) # 3 从下标 2 开始找
xs.count(20) # 2
99 in xs # False
# 找不到 index 会抛 ValueError,先判 in 或 try:
if 20 in xs:
i = xs.index(20)list.index(x) 返回第一个匹配下标(没有就抛 ValueError);可选 start 参数从指定位置继续找。count(x) 数出现次数。
import bisect xs = [1, 3, 5, 7, 9] bisect.bisect_left(xs, 5) # 2 5 应插入的最左位置 bisect.bisect_right(xs, 5) # 3 最右位置 bisect.insort(xs, 4) # 保持有序地插入 -> [1,3,4,5,7,9] # 用 bisect 把分数转等级: grades = "FDCBA" i = bisect.bisect([60, 70, 80, 90], score) grade = grades[i]
bisect 在已排序列表上做 O(log n) 二分。insort 保持有序插入。经典用法:把分数映射到等级区间。
import heapq h = [5, 1, 3] heapq.heapify(h) # 原地建小顶堆 heapq.heappush(h, 2) heapq.heappop(h) # 1 弹最小 heapq.nlargest(3, nums) # 最大的 3 个 heapq.nsmallest(3, nums) # 最小的 3 个 # 优先队列(用元组,第一个元素当优先级): heapq.heappush(pq, (priority, task)) # 最大堆:存负数 heapq.heappush(maxh, -value)
heapq 是小顶堆。heapify 原地建堆,heappush/heappop 进出。求 top-k 用 nlargest/nsmallest 比整体排序快。要大顶堆就存负数。
from collections import deque
q = deque([1, 2, 3])
q.append(4) # 右进
q.appendleft(0) # 左进
q.pop() # 右出
q.popleft() # 左出 (list.pop(0) 是 O(n),deque 是 O(1))
q.rotate(1) # 整体右转一位
# 定长滑动窗口:
window = deque(maxlen=3)
for x in stream:
window.append(x) # 超过 3 个自动挤掉最左deque 两端都是 O(1) 进出,队列和 BFS 用它,别用 list(list.pop(0) 是 O(n))。maxlen 做自动挤出的定长滑动窗口。
nested = [[1, 2], [3, 4], [5]]
# 一层嵌套:
flat = [x for row in nested for x in row] # [1,2,3,4,5]
# 或 itertools:
from itertools import chain
flat = list(chain.from_iterable(nested))
# 任意深度递归展平:
def flatten(xs):
for x in xs:
if isinstance(x, list):
yield from flatten(x)
else:
yield x一层嵌套用双 for 推导或 chain.from_iterable 展平。任意深度用 yield from 写递归生成器。
def chunks(xs, n):
for i in range(0, len(xs), n):
yield xs[i:i + n]
list(chunks([1, 2, 3, 4, 5], 2)) # [[1,2],[3,4],[5]]
# 配对(3.12+ 用 itertools.batched):
from itertools import batched # Python 3.12+
list(batched("ABCDEFG", 3)) # [('A','B','C'),('D','E','F'),('G',)]range(0, len, n) 每隔 n 切片即可分块。3.12+ 自带 itertools.batched(iterable, n),对任意可迭代都行,不限于可切片的列表。
d = {"name": "Lei", "age": 30}
d["name"] # "Lei"
d["email"] = "x@y.z" # 加键
del d["age"] # 删键
"name" in d # 判键存在
len(d)
list(d.keys()), list(d.values()), list(d.items())dict 是哈希表,平均 O(1) 查找。3.7 起 dict 保证插入顺序,这是语言规范,不是实现细节,可放心依赖。
d = {"a": 1}
d["b"] # KeyError
d.get("b") # None
d.get("b", 0) # 0 (默认值)
# 嵌套取值的常见写法:
d.get("user", {}).get("name", "anonymous")d[k] 找不到抛 KeyError;d.get(k, 默认) 返回默认。嵌套取值时链式 .get("x", {}).get(...) 是常见安全写法。
groups = {}
for name, group in pairs:
groups.setdefault(group, []).append(name)
# 等价于:
from collections import defaultdict
groups = defaultdict(list)
for name, group in pairs:
groups[group].append(name)setdefault 有就返回,没有就设并返回默认值。defaultdict(list) 是更优雅的分组写法。
a = {"x": 1, "y": 2}
b = {"y": 20, "z": 30}
a.update(b) # a 变成 {"x":1, "y":20, "z":30}
# 3.5+ 解包合并(返回新字典):
merged = {**a, **b}
# 3.9+ | 运算符:
merged = a | b
a |= b # 原地合并update 原地修改 a。{**a, **b} 或 3.9+ 的 a | b 返回新字典。冲突时后者覆盖前者。
{x: x * x for x in range(5)} # {0:0, 1:1, 2:4, 3:9, 4:16}
{k: v for k, v in items if v is not None} # 过滤 None 值
{v: k for k, v in d.items()} # 反转 kv
# 多个源:
{k: a[k] + b[k] for k in a.keys() & b.keys()} # 共同键求和语法和 list 推导一样,但是 key:value。常用于过滤 None、反转 kv、临时建查表。
from collections import Counter
c = Counter("abracadabra")
# Counter({'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1})
c.most_common(3) # [('a', 5), ('b', 2), ('r', 2)]
c1 + c2 # 计数相加
c1 - c2 # 计数相减(负数被截)
c1 & c2, c1 | c2 # 取最小 / 最大Counter 是专门做计数的 dict 子类。most_common(n) 给前 n 个。支持 + - & | 做多重集合运算。
from collections import defaultdict
counts = defaultdict(int) # 默认 0
for w in words:
counts[w] += 1 # 不用先判 in
buckets = defaultdict(list)
for x in items:
buckets[x.group].append(x)
nested = defaultdict(lambda: defaultdict(int)) # 嵌套 dictdefaultdict 首次访问就自动创建默认值。defaultdict(int) 计数,defaultdict(list) 分组,lambda 套娃做嵌套。
from collections import OrderedDict
od = OrderedDict()
od["a"] = 1
od["b"] = 2
od.move_to_end("a") # 移到末尾
od.popitem(last=False) # 弹首元素(普通 dict 没这俩方法)3.7 后普通 dict 已经保序,大部分场景 OrderedDict 可换成 dict。要用 move_to_end / popitem(last=False) 才有必要保留。
d = {"a": 1, "b": 2, "c": 3}
d.pop("a") # 1 删并返回值
d.pop("x", None) # None 给默认值就不抛 KeyError
d.popitem() # ('c', 3) 弹最后插入的 (3.7+ LIFO)
d.clear() # 清空
# 安全地"取出并删除":
val = d.pop("key", "default")pop(k) 删并返回;pop(k, 默认) 不抛 KeyError。popitem() 弹最后插入的键值对(3.7 起 LIFO)。clear() 原地清空。
a = {"x": 1, "y": 2}
b = {"y": 3, "z": 4}
a.keys() & b.keys() # {'y'} 共同键
a.keys() | b.keys() # {'x','y','z'}
a.keys() - b.keys() # {'x'} 只在 a 里
# keys() 是动态视图,dict 变它也变:
ks = a.keys()
a["w"] = 9
list(ks) # 含 'w' 了dict.keys()/items() 是动态视图,dict 改了它也跟着变;keys() 直接支持集合运算符(& | -),比对两个字典差异很方便。
from collections import ChainMap
defaults = {"color": "red", "size": "M"}
overrides = {"color": "blue"}
cfg = ChainMap(overrides, user, defaults)
cfg["color"] # "blue" 按顺序第一个命中的赢
cfg["size"] # "M" 回退到 defaults
# 不复制、不合并,按优先级链式查找
dict(cfg) # 需要时再拍平成普通 dictChainMap 按顺序在多个字典里查,不复制不合并,第一个命中的赢。做分层配置(命令行 > 环境 > 文件 > 默认)特别合适。
scores = {"lei": 90, "han": 85, "mei": 95}
# 按值降序:
sorted(scores.items(), key=lambda kv: kv[1], reverse=True)
# [('mei',95),('lei',90),('han',85)]
# 只要最高的人:
max(scores, key=scores.get) # 'mei'
# Top 2:
import heapq
heapq.nlargest(2, scores.items(), key=lambda kv: kv[1])sorted(d.items(), key=lambda kv: kv[1]) 按值排。max(d, key=d.get) 拿最大值对应的键。Top-N 键值对用 heapq.nlargest。
s = {1, 2, 3}
empty = set() # 注意:{} 是 dict 不是 set
s.add(4)
s.discard(99) # 不抛错;remove(99) 会抛 KeyError
2 in s # O(1)
len(s)set 无序、唯一、O(1) 成员判断。空集合是 set() 不是 {}({} 是空 dict)。discard() 是不抛错版的 remove()。
a = {1, 2, 3}
b = {2, 3, 4}
a | b # 并集 {1, 2, 3, 4}
a & b # 交集 {2, 3}
a - b # 差集 {1}
a ^ b # 对称差 {1, 4}
a <= b # 子集判断
a >= b # 超集判断set 运算符最易读。也有 .union() .intersection() 等方法名版,可读性上运算符赢。
fs = frozenset([1, 2, 3])
# 可以做 dict 的键、放进 set 里
d = {fs: "value"}
s = {frozenset([1, 2]), frozenset([3, 4])}frozenset 不可变可哈希,能当 dict 的 key、能放进 set 里。普通 set 都不行。
# 简单去重,不保留顺序: list(set([1, 2, 2, 3, 1])) # 顺序不稳定 # 保留顺序去重(3.7+ dict 保序): list(dict.fromkeys([1, 2, 2, 3, 1])) # [1, 2, 3]
set() 会丢顺序,dict.fromkeys() 保留首次出现顺序(3.7+ dict 保序)。
{x * x for x in range(10)} # {0, 1, 4, 9, ..., 81}
{w.lower() for w in words} # 去重 + 小写
# 一句话拿独立词数:
unique_words = len({w.lower() for w in text.split()})语法和 list / dict 推导一样,用 {} 但没 key:。算"去重总数"一行搞定。
a = {1, 2, 3}
a.update([3, 4, 5]) # a |= {...} 并入
a.intersection_update({2,4}) # a &= {...} 只留交集
a.difference_update({2}) # a -= {...} 减去
a.discard(99) # 不存在也不报错
a.pop() # 随机弹一个 (set 无序)
# 子集/超集判断:
{1, 2}.issubset({1, 2, 3}) # True
{1, 2, 3}.isdisjoint({4, 5}) # True 没有交集*_update 方法(或 |= &= -=)原地修改集合。isdisjoint() 判断是否无交集,不用真去算交集。pop() 弹任意一个元素(set 无序)。
old = ["a", "b", "c"]
new = ["b", "c", "d"]
added = set(new) - set(old) # {'d'} 新增
removed = set(old) - set(new) # {'a'} 删除
common = set(old) & set(new) # {'b','c'} 保留
# 注意:会丢顺序和重复,只看"存在性"
# 要去重 + 保序:list(dict.fromkeys(...))把两列表转 set 求差异:new - old 是新增,old - new 是删除,& 是保留。注意会丢顺序和重复,只看存在性。
t = (1, 2, 3) t = 1, 2, 3 # 括号可省,逗号才是关键 single = (1,) # 单元素元组要加逗号! empty = () # 不可变,不能改: # t[0] = 99 → TypeError a, b, c = t # 拆包
元组靠逗号定义,不是括号。单元素元组要写 (1,),(1) 只是数字 1,最常见的坑。
from collections import namedtuple
Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)
p.x, p.y # 3, 4
p[0] # 3 (也支持索引)
p._asdict() # {"x": 3, "y": 4}
Point(**{"x": 5, "y": 6})namedtuple 给元组加字段名,不可变、轻、能 pickle。要默认值、加方法就改用 dataclass。
from dataclasses import dataclass, field
@dataclass
class User:
name: str
age: int = 0
tags: list[str] = field(default_factory=list) # 可变默认值必须 factory
u = User("Lei", 30)
u.age = 31 # 默认可变
@dataclass(frozen=True) # 不可变版本
class Point:
x: float
y: floatdataclass(3.7+)自动生成 __init__ / __repr__ / __eq__。可变默认值必须 field(default_factory=list),否则中招"可变默认参数"坑。frozen=True 出不可变版本。
a, b, c = (1, 2, 3)
first, *rest = [1, 2, 3, 4] # 1, [2, 3, 4]
*init, last = [1, 2, 3, 4] # [1, 2, 3], 4
a, *_, b = range(10) # 头尾,中间扔掉
# 函数返回多值常用:
def divmod_(a, b):
return a // b, a % b
q, r = divmod_(10, 3)解构带 *rest 能接中间或末尾。函数返回多值其实就是返回元组,调用端再解构出来。
from typing import NamedTuple
class Point(NamedTuple):
x: int
y: int
label: str = "origin" # 带默认值
p = Point(3, 4)
p.x, p.label # 3, "origin"
p._replace(x=10) # 返回新元组(不可变)
# 比 collections.namedtuple 多了类型注解,更现代typing.NamedTuple 用类语法写带类型注解和默认值的 namedtuple。._replace() 返回修改后的新元组(元组不可变)。比函数式写法更现代。
from dataclasses import dataclass, asdict, replace, field
@dataclass
class Rect:
w: float
h: float
area: float = field(init=False) # 不进 __init__
def __post_init__(self):
self.area = self.w * self.h # 派生字段
r = Rect(3, 4)
r.area # 12
asdict(r) # {'w':3,'h':4,'area':12}
replace(r, w=5) # 返回改了 w 的新实例__post_init__ 在生成的 __init__ 之后跑,用来算派生字段或校验。field(init=False) 让字段不进构造器。asdict()/replace() 是 dataclass 配套工具。
from enum import Enum, auto
class Color(Enum):
RED = 1
GREEN = 2
BLUE = auto() # 自动 3
Color.RED # <Color.RED: 1>
Color.RED.name # 'RED'
Color.RED.value # 1
Color(1) # <Color.RED: 1> 按值反查
list(Color) # 可迭代
# 3.11+ StrEnum:成员就是字符串
from enum import StrEnum
class Env(StrEnum):
DEV = "dev"Enum 提供带 .name/.value 的具名常量,Color(1) 按值反查,还可迭代。auto() 自动递增赋值。3.11+ 加了 StrEnum/IntEnum,成员直接当字符串/整数用。
if x > 0:
print("正")
elif x < 0:
print("负")
else:
print("零")
# 三元(条件表达式):
label = "正" if x > 0 else "非正"标准分支。Python 写 elif 不是 else if。三元:值1 if 条件 else 值2,条件在中间,跟 C/JS 不一样。
for x in items:
if x < 0:
break
else:
print("没遇到负数") # break 没触发才会跑
for i in range(5): print(i)
for i in range(2, 10, 2): print(i) # 2, 4, 6, 8for-else:循环没被 break 才会跑 else,写"找不到就报告"的逻辑很简洁。
while queue:
x = queue.pop(0)
if x is None:
continue
if x == "stop":
break
process(x)
# while-else 同样:else 在没 break 时跑while 条件为真就一直跑。break 跳出整个循环,continue 跳过本次。while-else 也存在,逻辑同 for-else。
def http_status(code: int) -> str:
match code:
case 200 | 201 | 204:
return "OK"
case 301 | 302:
return "Redirect"
case 400 | 404:
return "Client error"
case n if 500 <= n < 600: # guard
return "Server error"
case _:
return "Unknown"
# 解构匹配:
match point:
case (0, 0): return "原点"
case (x, 0): return f"X 轴 {x}"
case (0, y): return f"Y 轴 {y}"
case (x, y): return f"({x},{y})"3.10+ 才有的结构化模式匹配。| 表示或,case ... if x 是 guard。能匹配元组、字典、类。最后写 case _: 当 default。
# 在 while 条件里赋值 + 判断:
while chunk := f.read(4096):
process(chunk)
# if 里赋值 + 用:
if (n := len(items)) > 10:
print(f"too many: {n}")
# 推导里复用计算结果:
[y for x in data if (y := expensive(x)) is not None]海象 := 既赋值又返回。最适合"循环条件里要拿值又要判"和"推导式里要复用计算结果"两种场景。
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
match obj:
case Point(x=0, y=0):
print("原点")
case Point(x=0, y=y):
print(f"Y 轴 {y}")
case {"type": "user", "name": name}: # 字典模式
print(f"用户 {name}")
case [first, *rest]: # 序列模式
print(first, rest)
case _:
print("其他")match 支持类模式(Point(x=0, y=y) 会绑定 y)、字典模式(匹配键的子集)、带 *rest 的序列模式。3.10+ 才有。
x = 5
0 < x < 10 # True 链式比较,等价 0 < x and x < 10
1 <= month <= 12
"a" <= ch <= "z" # 判小写字母
# in 判区间/集合:
if status in (200, 201, 204):
...
if grade in "ABCDF":
...Python 支持链式比较:0 < x < 10 等价 0 < x and x < 10,且 x 只算一次。用 in (a, b, c) 替代多个 == 的"等于其中之一"判断。
# 卫语句(guard clause)替代深层嵌套:
def process(user):
if user is None:
return None
if not user.active:
return None
# 主逻辑不用层层缩进
return do_work(user)
# 替代 if/else 金字塔,可读性大涨卫语句(无效输入提前 return)能把嵌套 if/else 金字塔拍平,边界情况先处理掉,主逻辑保持最浅缩进,可读性大涨。
def greet(name: str, greeting: str = "Hello") -> str:
return f"{greeting}, {name}!"
greet("Lei") # "Hello, Lei!"
greet("Lei", greeting="嗨") # 关键字参数
# 坑:可变默认值(永远不要这样写)
def add(item, items=[]): # ❌ 所有调用共享同一个 list
items.append(item)
return items
def add(item, items=None): # ✅ 正确写法
if items is None:
items = []
items.append(item)
return items默认参数在 def 时求值一次,千万别用 [] 或 {} 这种可变默认值。要用 None 然后在函数里初始化。
add = lambda a, b: a + b sorted(rows, key=lambda r: r[1]) list(filter(lambda x: x > 0, nums)) # lambda 只能写单个表达式,不能有 return / for / 多行 # 多行逻辑就老老实实 def
lambda 只能单表达式,不能有语句、不能多行。短回调可以用,逻辑长就老老实实 def。
def fn(*args, **kwargs):
print(args) # 元组
print(kwargs) # 字典
fn(1, 2, 3, name="Lei")
# 转发参数(装饰器常用):
def wrapper(*args, **kwargs):
return inner(*args, **kwargs)
# 强制关键字参数(* 之后必须 keyword):
def connect(host, *, port=80, ssl=False):
pass
connect("x", port=443) # ✅
# connect("x", 443) # ❌ TypeError*args 把位置参数收成元组,**kwargs 把关键字参数收成字典。裸 * 强制后面必须 keyword 调用,API 设计利器,避免位置参数搞混。
def greet(name: str, times: int = 1) -> str:
return ("Hi " + name + "! ") * times
from typing import Optional, Union, Callable
def get(key: str) -> Optional[str]: ...
# 3.10+ 简化:
def get(key: str) -> str | None: ...
# 3.9+ 内置泛型:
def parse(items: list[int]) -> dict[str, int]: ...
# 函数类型:
def apply(fn: Callable[[int, int], int], a: int, b: int) -> int:
return fn(a, b)type hint 不强制,但 mypy / pyright 会检查。3.9+ 可以直接写 list[int]、dict[str, int];3.10+ 用 X | Y 替代 Union。
from functools import wraps
import time
def timed(fn):
@wraps(fn) # 保留 fn 的 __name__ / docstring
def inner(*args, **kwargs):
t = time.perf_counter()
result = fn(*args, **kwargs)
print(f"{fn.__name__} took {time.perf_counter()-t:.3f}s")
return result
return inner
@timed
def slow():
time.sleep(1)装饰器是接受函数、返回函数的函数。一定要用 @functools.wraps 保留原函数的 __name__ 和 docstring,否则反射全废。
# 经典 late binding 坑: fns = [lambda: i for i in range(3)] [f() for f in fns] # [2, 2, 2],不是 [0, 1, 2] # 修复:默认参数当时绑定 fns = [lambda i=i: i for i in range(3)] [f() for f in fns] # [0, 1, 2]
循环里的 lambda 按引用捕获变量,不是按值,最后全看到末值。修复:写 lambda i=i: i,用默认参数把当前值固定下来。
count = 0
def inc():
global count # 不写就报 UnboundLocalError
count += 1
def make_counter():
n = 0
def inc():
nonlocal n # 修改外层函数的 n(不是 global)
n += 1
return n
return inc函数内读外层变量 OK,但要赋值必须声明 global(模块级)或 nonlocal(外层函数),不然就抛 UnboundLocalError。
from functools import partial
def power(base, exp):
return base ** exp
square = partial(power, exp=2)
cube = partial(power, exp=3)
square(5) # 25
cube(3) # 27
# 实战:给 sorted 传带固定参数的 key
sorted(rows, key=partial(getattr, default=None))partial 把一部分参数预先绑死,返回新可调用。比写 lambda 干净,专门用于参数固定的场景。
def div(a, b, /, *, precision=2):
return round(a / b, precision)
div(10, 3) # ✅
div(10, 3, precision=4) # ✅
# div(a=10, b=3) # ❌ a,b 在 / 前,禁用关键字
# / 之前:仅位置 * 之后:仅关键字 中间:都行裸 /(3.8+)把它前面的参数标为仅位置,调用方不能用名字传。配合 *,你能完全掌控 API 的调用方式。
from functools import wraps
def retry(times=3):
def deco(fn):
@wraps(fn)
def inner(*args, **kwargs):
for i in range(times):
try:
return fn(*args, **kwargs)
except Exception:
if i == times - 1:
raise
return inner
return deco
@retry(times=5)
def flaky(): ...带参装饰器是"返回装饰器的函数",三层嵌套。@retry(times=5) 先调最外层工厂拿到装饰器,再去装饰函数。
from functools import singledispatch
@singledispatch
def to_json(obj):
raise TypeError(f"不支持 {type(obj)}")
@to_json.register
def _(obj: int):
return str(obj)
@to_json.register
def _(obj: list):
return "[" + ",".join(map(to_json, obj)) + "]"
to_json(42) # "42"
to_json([1, 2]) # "[1,2]"singledispatch 让函数按第一个参数的类型选实现,Python 内置的单分派多态,比一堆 isinstance 判断干净。
from functools import reduce
import math, operator
# 求和优先用内置 sum:
sum([1, 2, 3, 4]) # 10
# 求积(3.8+):
math.prod([1, 2, 3, 4]) # 24
# 通用折叠用 reduce + operator:
reduce(operator.mul, [1,2,3,4], 1) # 24
reduce(operator.or_, [{1}, {2}, {3}]) # {1,2,3} 合并集合优先用内置:求和用 sum(),求积用 math.prod()(3.8+)。只有非平凡折叠才用 reduce(),配 operator 模块更清晰。
import sys
sys.getrecursionlimit() # 默认 1000
sys.setrecursionlimit(10000) # 谨慎调大,可能栈溢出崩溃
# Python 不做尾递归优化,深递归改迭代:
def factorial(n): # 递归版,n 大会 RecursionError
return 1 if n <= 1 else n * factorial(n - 1)
def factorial_iter(n): # 迭代版,安全
r = 1
for i in range(2, n + 1):
r *= i
return rPython 不做尾递归优化,深递归会触发 RecursionError(约 1000 层)。热点递归改写成迭代;调大递归限制是下策,可能直接栈溢出崩。
class User:
def __init__(self, name: str, age: int):
self.name = name
self.age = age
def greet(self) -> str:
return f"Hi, I'm {self.name}"
u = User("Lei", 30)
u.greet()__init__ 是创建实例后的初始化。self 是当前实例,约定俗成,但每个方法定义都必须显式带上。
class Circle:
def __init__(self, r):
self.r = r
@property
def area(self):
return 3.14159 * self.r ** 2
@area.setter
def area(self, value):
self.r = (value / 3.14159) ** 0.5
c = Circle(5)
c.area # 不用 c.area(),直接像属性一样访问
c.area = 100 # 触发 setter@property 把方法变成属性访问。@prop.setter 定义写入逻辑。需要校验或派生时用,比 getter/setter 函数地道。
class User:
def __init__(self, name):
self.name = name
@classmethod
def from_dict(cls, d): # 工厂方法
return cls(d["name"])
@staticmethod
def is_valid_name(s): # 与类相关但不需要 cls / self
return s.isalpha()
u = User.from_dict({"name": "Lei"})
User.is_valid_name("Lei")classmethod 接收 cls(类本身),用于工厂方法。staticmethod 啥都不接收,是放在类命名空间里的工具函数。
class Money:
def __init__(self, amount, currency):
self.amount = amount
self.currency = currency
def __repr__(self):
return f"Money({self.amount}, '{self.currency}')"
def __str__(self):
return f"{self.amount:.2f} {self.currency}"
def __eq__(self, other):
return (self.amount, self.currency) == (other.amount, other.currency)
def __hash__(self):
return hash((self.amount, self.currency))
def __lt__(self, other):
return self.amount < other.amount
def __add__(self, other):
return Money(self.amount + other.amount, self.currency)__repr__ 给调试看,__str__ 给用户看;__eq__/__hash__ 让对象能进 set / dict 当 key;__lt__ 让 sorted() 能排;__add__ 让 + 工作。
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return "..."
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # 调父类 __init__
self.breed = breed
def speak(self): # 覆盖
return "Woof!"继承写 class Sub(Base)。super().method() 调父类。多继承按 MRO(方法解析顺序)。
from dataclasses import dataclass
@dataclass(frozen=True, slots=True) # 3.10+ slots=True
class Point:
x: float
y: float
p = Point(3, 4)
# p.x = 99 → FrozenInstanceError 不可变
# p.z = 99 → AttributeError slots 禁止新属性
hash(p) # frozen=True 自动可哈希frozen=True 让实例不可变且可哈希。slots=True(3.10+)用 __slots__ 省内存,且能防止打错字新建属性。
class Timer:
def __enter__(self):
import time
self.t = time.perf_counter()
return self
def __exit__(self, exc_type, exc, tb):
import time
print(f"耗时 {time.perf_counter() - self.t:.3f}s")
return False # False / None 表示不吞异常
with Timer():
slow_op()
# 或用 @contextmanager 装饰生成器:
from contextlib import contextmanager
@contextmanager
def open_db():
conn = connect()
try:
yield conn
finally:
conn.close()类实现 __enter__/__exit__,或更简单:写生成器 + @contextmanager。清理逻辑放 finally / __exit__ 才稳。
class Dog:
species = "Canis" # 类属性,所有实例共享
def __init__(self, name):
self.name = name # 实例属性,各自独立
a, b = Dog("A"), Dog("B")
Dog.species # "Canis"
a.species = "Wolf" # 在 a 上新建实例属性,遮蔽类属性
b.species # 仍 "Canis"
# 坑:类属性用可变对象会被所有实例共享!
class Bad:
items = [] # ❌ 所有实例共用一个 list类属性所有实例共享;给 self.x 赋值会在实例上新建一个遮蔽类属性的同名属性。坑:可变类属性(items = [])会被所有实例共用。
class Point:
__slots__ = ("x", "y") # 不再有 __dict__
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(3, 4)
# p.z = 5 → AttributeError 禁止新属性
# 大量实例时省内存(不为每个对象建 __dict__),
# 还能拦住打错字凭空建属性的 bug__slots__ 用固定布局替代每个实例的 __dict__,大量实例时省内存,还能拦住打错字凭空建属性的 bug。代价是不能再动态加属性。
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self) -> float: ...
class Circle(Shape):
def __init__(self, r): self.r = r
def area(self): return 3.14159 * self.r ** 2
# Shape() → TypeError 不能实例化抽象类
# 忘实现 area 的子类也不能实例化
c = Circle(5)继承 ABC 并用 @abstractmethod 标记方法,强制子类实现,直接实例化抽象类或没实现全的子类都会抛 TypeError。
class Multiplier:
def __init__(self, factor):
self.factor = factor
def __call__(self, x):
return x * self.factor
double = Multiplier(2)
double(10) # 20 像函数一样调用实例
# 适合"带状态的函数":计数器、累加器、配置好的转换器
callable(double) # True定义 __call__ 让实例像函数一样调用,适合"带状态的函数"(配置好的转换器、计数器)。callable(obj) 检测能不能调用。
class Deck:
def __init__(self, cards):
self._cards = cards
def __len__(self):
return len(self._cards)
def __getitem__(self, i):
return self._cards[i] # 支持 deck[0]、切片、for、in
deck = Deck(["A", "K", "Q"])
len(deck) # 3
deck[0] # "A"
for c in deck: ... # __getitem__ 即可迭代
"A" in deck # True 自动支持实现 __len__ + __getitem__,类就表现得像序列,索引、切片、for 迭代、in 全部白送,连 __iter__ 都不用写。
from functools import total_ordering
@total_ordering
class Version:
def __init__(self, n): self.n = n
def __eq__(self, o): return self.n == o.n
def __lt__(self, o): return self.n < o.n
# > >= <= 全部自动补出来
Version(1) < Version(2) # True
Version(2) >= Version(1) # True (自动生成)只写 __eq__ 和 __lt__/__le__/__gt__/__ge__ 中的一个,@total_ordering 自动补全其余比较运算符,全序类型少写一堆样板。
# 读文本(自动指定 encoding,别依赖系统默认!)
with open("a.txt", "r", encoding="utf-8") as f:
content = f.read()
# 一行行读(大文件友好,不全部加载)
with open("big.log", encoding="utf-8") as f:
for line in f:
process(line.rstrip("\n"))
# 写文本
with open("out.txt", "w", encoding="utf-8") as f:
f.write("hello\n")
f.writelines(["a\n", "b\n"])
# 追加
with open("out.txt", "a", encoding="utf-8") as f:
f.write("more\n")
# 二进制
with open("img.png", "rb") as f:
data = f.read()永远用 with 打开,出异常也会关。永远显式传 encoding="utf-8",别依赖系统默认(不同系统差异巨大,Windows 默认 cp936 害死人)。
from pathlib import Path
p = Path("data") / "users" / "lei.json" # / 操作符拼路径
p.exists()
p.is_file(), p.is_dir()
p.parent, p.name, p.stem, p.suffix
p.read_text(encoding="utf-8")
p.write_text("hello", encoding="utf-8")
list(Path(".").glob("*.py")) # 通配
list(Path(".").rglob("*.py")) # 递归
Path("out").mkdir(parents=True, exist_ok=True)pathlib(3.4+)用 Path 对象替代 os.path 字符串拼接。用 / 拼路径,永远别字符串相加或 os.path.join。
import json
# 字符串 ↔ 对象
data = json.loads('{"a": 1}')
s = json.dumps({"a": 1}) # 紧凑
s = json.dumps(obj, indent=2, ensure_ascii=False) # 缩进 + 中文不转义
# 文件 ↔ 对象
with open("data.json", encoding="utf-8") as f:
data = json.load(f)
with open("out.json", "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False)load/dump 操作文件,loads/dumps 操作字符串。有中文一定加 ensure_ascii=False,否则全变成 \u 转义。
import csv
# 读
with open("data.csv", encoding="utf-8", newline="") as f:
reader = csv.DictReader(f)
for row in reader:
print(row["name"], row["age"])
# 写
with open("out.csv", "w", encoding="utf-8", newline="") as f:
writer = csv.DictWriter(f, fieldnames=["name", "age"])
writer.writeheader()
writer.writerow({"name": "Lei", "age": 30})CSV 文件打开一定加 newline="",否则 Windows 会插空行。DictReader / DictWriter 自动处理表头。
import tempfile
# 临时文件(自动删除):
with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as f:
f.write("data")
path = f.name
# 临时目录:
with tempfile.TemporaryDirectory() as d:
p = Path(d) / "x.txt"
p.write_text("hi")
# 出 with 时 d 整个被删tempfile 让 OS 管理临时路径。NamedTemporaryFile / TemporaryDirectory 出 with 自动清理,省心。
import os, shutil
os.path.exists("a.txt")
os.makedirs("a/b/c", exist_ok=True) # 递归建目录
os.rename("old.txt", "new.txt")
os.remove("a.txt") # 删文件
os.listdir(".")
shutil.copy("src.txt", "dst.txt") # 复制文件
shutil.copytree("src/", "dst/") # 递归复制目录
shutil.move("a/", "b/") # 移动
shutil.rmtree("dir/") # 递归删目录 (危险!)os 管单文件操作(rename、remove、makedirs);shutil 管递归/高级操作(copytree、move、rmtree)。路径拼接还是优先用 pathlib。
import glob
glob.glob("*.py") # 当前目录 .py
glob.glob("src/**/*.py", recursive=True) # 递归
glob.glob("data/[0-9]*.csv") # 字符集
# pathlib 等价写法(更推荐):
from pathlib import Path
list(Path("src").rglob("*.py"))
list(Path(".").glob("*.txt"))glob 按通配符匹配路径:* 任意、? 单字符、[..] 字符集、** 配 recursive=True 任意深度。pathlib 的 .glob/.rglob 是更现代的等价写法。
import pickle
data = {"users": [1, 2, 3], "obj": SomeClass()}
# 存:
with open("data.pkl", "wb") as f:
pickle.dump(data, f)
# 读:
with open("data.pkl", "rb") as f:
data = pickle.load(f)
# 字节串:
b = pickle.dumps(data)
# ⚠️ 永远不要 pickle.load 不可信来源,可执行任意代码pickle 能把几乎任意 Python 对象序列化成字节(用 "wb"/"rb")。安全警告:绝不 load 不可信来源的 pickle,会执行任意代码。跨系统交换数据用 JSON。
import configparser
cfg = configparser.ConfigParser()
cfg.read("app.ini", encoding="utf-8")
cfg["database"]["host"] # 取值(都是字符串)
cfg.getint("database", "port") # 转 int
cfg.getboolean("flags", "debug") # 转 bool
cfg.sections() # 所有节
# 写:
cfg["new"] = {"key": "value"}
with open("app.ini", "w") as f:
cfg.write(f)configparser 读写 INI 文件。取出来都是字符串,要类型用 getint/getfloat/getboolean。节(section)用起来像嵌套字典。
import gzip, zipfile
# gzip 单文件:
with gzip.open("data.json.gz", "wt", encoding="utf-8") as f:
f.write(json_str)
with gzip.open("data.json.gz", "rt", encoding="utf-8") as f:
content = f.read()
# zip 多文件:
with zipfile.ZipFile("out.zip", "w", zipfile.ZIP_DEFLATED) as z:
z.write("a.txt")
z.writestr("b.txt", "内容") # 直接写字符串
with zipfile.ZipFile("out.zip") as z:
z.extractall("dest/")
names = z.namelist()gzip.open 压缩单个流(文本用 "rt"/"wt")。zipfile 打包多文件,ZIP_DEFLATED 开压缩,writestr 直接写内容,extractall 解包。
try:
n = int(s)
data = fetch(n)
except ValueError as e:
print(f"输入不是数字: {e}")
except (KeyError, IndexError) as e: # 多种异常一起
print(f"数据问题: {e}")
except Exception as e: # 兜底
print(f"其他错误: {e}")
raise # 抛出去
else:
print("没出错才跑这里")
finally:
cleanup() # 出不出错都跑else 只有没出异常才跑。finally 不管有没有异常都跑(清理)。裸 except: 会吞掉 Ctrl+C,几乎永远是错的,要用 except Exception:。
raise ValueError("age 不能为负")
raise ValueError(f"非法值 {x}")
# 包装别人的异常(保留原始 traceback)
try:
int(s)
except ValueError as e:
raise ParseError("解析失败") from e
# 想隐藏原始链:
raise ParseError("...") from Noneraise X from Y 包装 Y 异常并保留链,traceback 两层都看得到。raise X from None 隐藏原始(很少需要)。
class APIError(Exception):
"""所有 API 错误的基类"""
class RateLimitError(APIError):
def __init__(self, retry_after: int):
super().__init__(f"rate limited, retry after {retry_after}s")
self.retry_after = retry_after
try:
call_api()
except RateLimitError as e:
time.sleep(e.retry_after)
except APIError:
log.exception("api failed")每个包定义一个基类异常,再派生子类,调用方能选择抓"全家族"或具体类型。继承 Exception,不要继承 BaseException。
from contextlib import suppress
# 老写法:
try:
os.remove("not-exist.txt")
except FileNotFoundError:
pass
# 新写法:
with suppress(FileNotFoundError):
os.remove("not-exist.txt")
# 多个异常:
with suppress(KeyError, IndexError):
value = data["k"][0]suppress(异常类型) 静默吞掉指定异常,一行操作要忽略错误时比 try/except/pass 干净。
def divide(a, b):
assert b != 0, "除数不能为 0"
return a / b
assert isinstance(x, int)
assert all(s > 0 for s in scores), f"有非正分数: {scores}"
# ⚠️ python -O 会去掉所有 assert,不要用 assert 做线上输入校验!
# 它是给开发期的"不变量检查"assert 条件, 信息 在条件为假时抛 AssertionError。警告:python -O 会删掉所有 assert,别用它做线上输入校验,它只是开发期的不变量检查。
def read_config():
f = open("config.txt")
try:
return f.read() # return 后 finally 仍会执行
finally:
f.close() # 保证关闭
# ⚠️ finally 里 return 会盖掉 try 里的 return / 吞掉异常:
def bad():
try:
return 1
finally:
return 2 # 实际返回 2,坑!finally 永远执行(return 后、异常中也跑),是释放资源的地方。坑:finally 里写 return 会盖掉 try 的 return,还会吞掉异常。
import logging
logging.basicConfig(level=logging.INFO)
log = logging.getLogger(__name__)
try:
risky()
except Exception:
log.exception("处理失败") # 自动附上完整 traceback
# 等价于 log.error("...", exc_info=True)
# traceback 模块手动取堆栈字符串:
import traceback
tb_str = traceback.format_exc()except 块里 log.exception(信息) 会自动带上完整 traceback。需要堆栈字符串时用 traceback.format_exc()。
# 3.11+ 一次抛多个异常(并发任务常见):
raise ExceptionGroup("多个失败", [
ValueError("bad value"),
TypeError("bad type"),
])
# 用 except* 分别处理:
try:
async with asyncio.TaskGroup() as tg:
...
except* ValueError as eg:
print("值错误组:", eg.exceptions)
except* TypeError as eg:
print("类型错误组:", eg.exceptions)ExceptionGroup(3.11+)把同时抛出的多个异常打包(比如 TaskGroup 里的)。except* 在组里分别匹配并处理每种类型。
it = iter([1, 2, 3])
next(it) # 1
next(it) # 2
next(it) # 3
next(it) # StopIteration
next(it, "默认") # 第二个参数避免 StopIteration
# 任何实现 __iter__ + __next__ 的对象都是迭代器
class CountUp:
def __init__(self, n): self.n = n; self.i = 0
def __iter__(self): return self
def __next__(self):
if self.i >= self.n: raise StopIteration
self.i += 1
return self.iiter(obj) 拿迭代器,next(it) 推进。next(it, 默认) 安全版避免 StopIteration。for 循环背后就是在调这两个。
def count_up(n):
i = 0
while i < n:
yield i # 每次 yield 暂停,下次 next() 继续
i += 1
for x in count_up(5): print(x)
# 生成器表达式(节省内存):
squares = (x * x for x in range(1_000_000))
sum(squares)yield 让函数变生成器,惰性产值、状态保留。生成器表达式用 () 不是 [],处理超大数据时省内存。
def flatten(nested):
for item in nested:
if isinstance(item, list):
yield from flatten(item) # 递归
else:
yield item
list(flatten([1, [2, [3, [4]]], 5])) # [1, 2, 3, 4, 5]yield from 把迭代委托给另一个可迭代对象,含其所有子 yield。比手写 for + yield 简洁。
from itertools import chain, groupby, permutations, combinations, product, accumulate, islice
list(chain([1, 2], [3, 4])) # [1, 2, 3, 4]
list(chain.from_iterable([[1, 2], [3, 4]])) # [1, 2, 3, 4]
list(permutations([1, 2, 3], 2)) # 排列 (1,2),(1,3),(2,1)...
list(combinations([1, 2, 3], 2)) # 组合 (1,2),(1,3),(2,3)
list(product([0, 1], repeat=3)) # 笛卡尔积
list(accumulate([1, 2, 3, 4])) # [1, 3, 6, 10] 前缀和
list(islice(big_iter, 10)) # 取前 10 个
# groupby 必须先排序!
rows = sorted(rows, key=itemgetter("group"))
for g, items in groupby(rows, key=itemgetter("group")):
print(g, list(items))itertools 是标准库的迭代器工具箱。最大的坑:groupby 只合并连续相等的元素,用之前必须先按 key 排序。
from functools import reduce, lru_cache, cache
reduce(lambda a, b: a + b, [1, 2, 3, 4]) # 10
reduce(lambda a, b: a + b, [1, 2, 3, 4], 100) # 110 (初值)
@lru_cache(maxsize=None) # 自动记忆
def fib(n):
return n if n < 2 else fib(n-1) + fib(n-2)
@cache # 3.9+ 简化版 lru_cache(maxsize=None)
def f(x): ...reduce 把二元运算累折叠。@lru_cache 自动记忆,能把指数递归变线性。@cache(3.9+)是 lru_cache(maxsize=None) 的简写。
from itertools import zip_longest, takewhile, dropwhile
list(zip_longest([1, 2], ["a", "b", "c"], fillvalue="-"))
# [(1, 'a'), (2, 'b'), ('-', 'c')] 长度对不齐用 fillvalue 填
list(takewhile(lambda x: x < 5, [1, 3, 4, 6, 2])) # [1, 3, 4] 遇到 False 就停
list(dropwhile(lambda x: x < 5, [1, 3, 4, 6, 2])) # [6, 2] 跳到第一个 Falsezip_longest 用 fillvalue 补齐短的一边。takewhile / dropwhile 在第一个不满足条件处切开,处理流式数据特别合适。
from itertools import count, cycle, repeat, islice
# 无限计数:
for i in count(10, 2): # 10, 12, 14, ...
if i > 20: break
# 循环:
colors = cycle(["red", "green", "blue"])
[next(colors) for _ in range(5)] # red,green,blue,red,green
# 重复:
list(repeat("x", 3)) # ['x','x','x']
# 配 islice 截断无限流:
list(islice(count(), 5)) # [0,1,2,3,4]count/cycle/repeat 是无限的,必须用 islice、takewhile 或 break 截断,绝不能直接 list()。cycle 把序列无限循环。
from itertools import starmap, pairwise, tee # starmap:参数已经成组时用,省去 lambda 解包 list(starmap(pow, [(2,3),(3,2)])) # [8, 9] # pairwise(3.10+):相邻配对 list(pairwise([1,2,3,4])) # [(1,2),(2,3),(3,4)] # 算相邻差: [b - a for a, b in pairwise(nums)] # tee:把一个迭代器复制成 n 个独立迭代器 it1, it2 = tee(iter(data), 2)
starmap(f, 元组列表) 把每个元组拆开当参数。pairwise(3.10+)给相邻对,算差分超方便。tee 把一个迭代器复制成 n 个独立的。
def averager():
total, count = 0, 0
avg = None
while True:
x = yield avg # yield 既出值又收 send 进来的值
total += x
count += 1
avg = total / count
g = averager()
next(g) # 预激活,跑到第一个 yield
g.send(10) # 10.0
g.send(20) # 15.0
g.close() # 关闭生成器生成器能在 yield 处用 .send() 接收值(yield 是双向的)。先用 next() 预激活。.close() 抛 GeneratorExit 关闭它。
names = ["Lei", "Han"]
ages = [30, 25]
# 同时要索引和多列:
for i, (name, age) in enumerate(zip(names, ages), start=1):
print(i, name, age)
# 反转字典遍历:
for v, k in sorted((v, k) for k, v in d.items()):
print(k, v)把 zip 套进 enumerate,一次拿到索引和多列并行值,记得给解包的 zip 元组加括号:enumerate(zip(a, b))。
import asyncio
async def fetch(url):
await asyncio.sleep(1) # 模拟 IO
return f"data from {url}"
async def main():
data = await fetch("https://x.com")
print(data)
asyncio.run(main()) # 入口必须这一行async def 定义协程,await 让出控制权。asyncio.run() 是入口,已经在事件循环里就不能再 run,会报错。
async def main():
urls = ["a", "b", "c"]
# 串行(慢):
results = [await fetch(u) for u in urls]
# 并发(快 N 倍):
results = await asyncio.gather(*[fetch(u) for u in urls])
# 一个失败别全部炸:
results = await asyncio.gather(*tasks, return_exceptions=True)
# 3.11+ TaskGroup(推荐写法,更安全):
async with asyncio.TaskGroup() as tg:
ts = [tg.create_task(fetch(u)) for u in urls]
results = [t.result() for t in ts]gather 并发跑多个协程。return_exceptions=True 让一个失败不连累其他。3.11+ TaskGroup 是更安全的现代写法,异常处理更干净。
# 立刻调度,不等:
task = asyncio.create_task(fetch("x"))
# 之后再 await:
result = await task
# 限时:3.11+
async with asyncio.timeout(5):
await long_op()
# 旧写法 3.10-:
try:
await asyncio.wait_for(long_op(), timeout=5)
except asyncio.TimeoutError:
...
# 等任一个完成:
done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)create_task 立刻调度。asyncio.timeout(3.11+)用 async with 包裹做超时;老代码用 wait_for。
# 异步迭代器
async def fetch_pages():
async for chunk in client.stream("..."):
yield chunk
# 异步上下文管理
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
text = await resp.text()async for / async with 用于实现了 __aiter__ / __aenter__ 的对象,HTTP 客户端、数据库驱动、流文件常见。
# Semaphore 限并发:
sem = asyncio.Semaphore(10)
async def fetch_limited(url):
async with sem:
return await fetch(url)
# Queue 生产消费:
q = asyncio.Queue(maxsize=100)
async def producer():
for x in items:
await q.put(x)
await q.put(None) # 哨兵
async def consumer():
while (x := await q.get()) is not None:
process(x)
q.task_done()Semaphore 限并发数(比如 HTTP 同时只跑 10 个)。Queue 是生产消费模型,用 None 当哨兵告诉消费者结束。
import asyncio
# 把同步阻塞函数丢到线程池,不堵事件循环(3.9+):
async def main():
result = await asyncio.to_thread(blocking_io_func, arg1, arg2)
# 比如读大文件、调同步的 requests 库
data = await asyncio.to_thread(open("big.txt").read)
# CPU 密集型用 run_in_executor + ProcessPoolExecutor
loop = asyncio.get_running_loop()
await loop.run_in_executor(pool, cpu_heavy, data)asyncio.to_thread(3.9+)把阻塞的同步函数丢到线程里跑,不卡事件循环,包旧的同步 IO 库正合适。CPU 密集型改用 ProcessPoolExecutor。
from contextlib import asynccontextmanager
@asynccontextmanager
async def get_conn():
conn = await connect()
try:
yield conn
finally:
await conn.close()
async def main():
async with get_conn() as conn:
await conn.query("...")
# 异步生成器:
async def gen():
for i in range(3):
await asyncio.sleep(0.1)
yield i@asynccontextmanager 把异步生成器变成 async with 资源。带 yield 的 async def 就是异步生成器,用 async for 消费。
import asyncio
# ✅ 顶层入口,跑完自动关循环:
asyncio.run(main())
# ❌ 已在协程里再调 run 会报错:
async def bad():
asyncio.run(other()) # RuntimeError
# 协程里直接 await:
async def good():
await other()
# 拿当前循环(3.10+):
loop = asyncio.get_running_loop()asyncio.run() 是唯一的顶层入口,会自建并关闭循环。在协程里再调它会抛 RuntimeError,直接 await 即可。
# 3.8 写法(typing 模块): from typing import List, Dict, Tuple, Set def fn(xs: List[int], d: Dict[str, int]) -> Tuple[int, ...]: ... # 3.9+ 推荐用内置(不用 import): def fn(xs: list[int], d: dict[str, int]) -> tuple[int, ...]: ... # tuple 长度固定写法: Tuple[int, str, float] # 三元素,类型不同 tuple[int, ...] # 任意长度,元素都是 int
3.9+ 可直接用 list[int] / dict[str, int] / set[X],不用 from typing import 了。typing 的别名(List/Dict)还能用但不必。
from typing import Optional, Union def get(k: str) -> Optional[str]: ... # str 或 None def x(v: Union[int, str]): ... # int 或 str # 3.10+ 推荐用 | : def get(k: str) -> str | None: ... def x(v: int | str): ... # Optional[T] === T | None # 它不是"可省略",而是"可能为 None",别误解
Optional[T] 是"可能为 None",不是"可省略参数",别误解。3.10+ 用 T | None / int | str 替代,更短。
from typing import TypedDict, NotRequired
class User(TypedDict):
name: str
age: int
email: NotRequired[str] # 3.11+
def greet(u: User) -> str:
return f"Hi, {u['name']}"
greet({"name": "Lei", "age": 30}) # ✅
# greet({"name": "Lei"}) # mypy 报错:缺 ageTypedDict 给字典形状加类型,JSON 风格数据特别适合,不用写整个类。3.11+ 的 NotRequired / Required 支持可选键。
from typing import Protocol
class HasName(Protocol):
name: str
def greet(self) -> str: ...
def shout(x: HasName) -> str:
return x.greet().upper()
# 任何有 name 属性 + greet() 方法的对象都满足 HasName
# 不用继承,结构匹配即可(structural typing)Protocol(3.8+)是带静态检查的鸭子类型,形状对就算实现,不用继承。Python 版的 Go interface。
from typing import TypeVar, Generic
T = TypeVar("T")
def first(xs: list[T]) -> T:
return xs[0]
class Stack(Generic[T]):
def __init__(self): self._items: list[T] = []
def push(self, x: T): self._items.append(x)
def pop(self) -> T: return self._items.pop()
# 3.12+ 新语法(不用 TypeVar):
def first[T](xs: list[T]) -> T:
return xs[0]TypeVar 声明泛型参数。Generic[T] 让类支持泛型。3.12+ 新语法更简洁:def f[T](xs: list[T])。
from typing import Literal, Final, Annotated
def open_file(mode: Literal["r", "w", "rb", "wb"]): ...
open_file("r") # ✅
# open_file("x") # mypy 报错
MAX: Final = 100 # 不可重新赋值
PI: Final[float] = 3.14159
# Annotated 给类型加元数据(pydantic / fastapi 常用):
Age = Annotated[int, "0-150"]Literal 限定具体字面量,模式参数特别好用。Final 标常量。Annotated 给类型加元数据,Pydantic / FastAPI 会读。
from typing import cast, overload
# 告诉类型检查器"我知道这里是什么"
x = cast(int, obj["count"]) # 运行时啥都不做,只骗 mypy
# 函数重载(多个签名,一份实现):
@overload
def fetch(x: int) -> User: ...
@overload
def fetch(x: str) -> list[User]: ...
def fetch(x):
# 真实实现
return _do(x)cast() 只骗 mypy,运行时啥都不做。@overload 声明多重签名(一份实现),适合返回类型随入参类型变的函数。
from typing import Callable
# 接收两个 int 返回 int 的函数:
def apply(fn: Callable[[int, int], int], a, b) -> int:
return fn(a, b)
# 任意参数:
handler: Callable[..., None]
# 3.10+ ParamSpec 保留被装饰函数的签名:
from typing import ParamSpec, TypeVar
P = ParamSpec("P"); R = TypeVar("R")
def deco(fn: Callable[P, R]) -> Callable[P, R]:
def inner(*args: P.args, **kwargs: P.kwargs) -> R:
return fn(*args, **kwargs)
return innerCallable[[参数类型], 返回] 给函数加类型;Callable[..., R] 接受任意参数。ParamSpec(3.10+)让装饰器在类型检查时保留被包函数的精确签名。
from __future__ import annotations # 注解延迟求值
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from .models import User # 只在类型检查时导入
def greet(u: User) -> str: # 字符串注解,运行时不真导入
return u.nameTYPE_CHECKING 运行时为 False、类型检查时为 True,把只为类型用的 import 放进去,打破循环导入。配 from __future__ import annotations 用。
from typing import Self # 3.11+
class Builder:
def add(self, x) -> Self: # 链式调用返回自身类型
self._items.append(x)
return self
def build(self): ...
Builder().add(1).add(2).build() # 类型推断正确
# 编译期断言类型(不影响运行):
from typing import assert_type
assert_type(parse("42"), int)Self(3.11+)给返回自身的链式/builder 方法标类型,能正确处理子类,比 TypeVar 干净。assert_type 运行时啥都不做,让类型检查器验证推断出的类型。
from typing import NewType
UserId = NewType("UserId", int)
OrderId = NewType("OrderId", int)
def get_user(uid: UserId): ...
uid = UserId(42)
get_user(uid) # ✅
# get_user(42) # mypy 报错:int 不是 UserId
# 运行时 UserId(42) 就是 42,零开销,只在类型层区分NewType 造一个运行时就是 int、但在类型检查器眼里独立的类型,防止把 UserId 和 OrderId 搞混(虽然底层都是 int)。零运行时开销。
# ❌ 错误(所有调用共享同一个 list):
def add(item, items=[]):
items.append(item)
return items
add(1) # [1]
add(2) # [1, 2] 不是 [2]!
# ✅ 正确:
def add(item, items=None):
if items is None:
items = []
items.append(item)
return items默认参数在 def 时求值一次,[] / {} / set() 这种可变默认值会被所有调用共享,调用间累积。永远用 None 哨兵 + 函数内初始化。
# ❌ 全是 2: fns = [lambda: i for i in range(3)] [f() for f in fns] # [2, 2, 2] # ✅ 默认参数当时固定: fns = [lambda i=i: i for i in range(3)] [f() for f in fns] # [0, 1, 2]
闭包按变量捕获不按值捕获,所有 lambda 看到的都是末值。用 lambda i=i: 把当前值冻在默认参数里。
import copy a = [[1, 2], [3, 4]] b = a # 同一个对象(引用) b = a[:] # 浅拷贝:外层新,内层共享 b = a.copy() # 浅拷贝 b = copy.copy(a) # 浅拷贝 b = copy.deepcopy(a) # 深拷贝(递归) a[0].append(99) print(b) # 浅拷贝下 b 也变了!
切片 / .copy() / copy.copy() 都是浅拷贝,内层对象共享。copy.deepcopy() 递归克隆整棵树。要不要深拷贝看内部对象会不会被修改。
a = [1, 2, 3] b = [1, 2, 3] a == b # True 值相等 a is b # False 不是同一个对象 # is 只对单例可靠:None / True / False / 小整数缓存等 x is None # ✅ 推荐 x == None # 能跑,不地道 # 坑:小整数缓存(-5 到 256) a = 256; b = 256 a is b # True (缓存) a = 257; b = 257 a is b # 一般是 False
is 比身份(同一对象),== 比值。只对 None / True / False 这种单例用 is。小整数缓存(-5~256)是 CPython 实现细节,永远别依赖。
# ❌ 边遍历边删,RuntimeError 或漏删
for x in list_:
if x.bad:
list_.remove(x)
# ✅ 倒序删 / 或新建一个:
for i in range(len(list_) - 1, -1, -1):
if list_[i].bad:
del list_[i]
xs = [x for x in xs if not x.bad] # 最地道
# dict 也同样:
for k in list(d.keys()): # list() 拷一份再删
if d[k] is None:
del d[k]边遍历边删容器会漏元素或抛 RuntimeError。要么先 list() 拷一份再遍历,要么用推导式新建一个。
count = 0
def inc():
count += 1 # ❌ UnboundLocalError
# 因为函数里赋值了 count,Python 把它当本地变量
def inc():
global count # ✅ 显式声明
count += 1函数里任何地方给 count 赋值,Python 就认它是本地变量(哪怕在赋值前用也算),不写 global / nonlocal 就 UnboundLocalError。
7 / 2 # 3.5 (Python 3 一律返回 float) 7 // 2 # 3 整除 -7 // 2 # -4 向下取整(不是向 0 取整!) divmod(7, 2) # (3, 1) 商和余数一起 # 来自 Python 2 的代码可能假设 / 是整除,升 3 时小心
Python 3 中 / 一律返回 float,// 是向下取整(不是向 0 取整:-7 // 2 是 -4 不是 -3)。divmod() 同时给商和余数。
# ❌ O(n²) — 字符串不可变,每次 + 都拷贝整段
s = ""
for x in items:
s += str(x)
# ✅ O(n) — 收集再 join
parts = []
for x in items:
parts.append(str(x))
s = "".join(parts)
# 或者列表推导 + join:
s = "".join(str(x) for x in items)字符串不可变,循环里 s += x 是 O(n²)(每次拷贝整段)。先收集到 list 再 "".join() 是 O(n),大数据量差距巨大。
# ❌ 吞掉 KeyboardInterrupt 和 SystemExit
try:
risky()
except:
pass
# ✅ 抓 Exception 就够了
try:
risky()
except Exception as e:
log.exception(e)裸 except 会抓 BaseException,连 Ctrl+C 和 sys.exit 都吞了。要用 except Exception: 抓正常错误就行。
0.1 + 0.2 == 0.3 # False!
0.1 + 0.2 # 0.30000000000000004
# ✅ 用 math.isclose 比较:
import math
math.isclose(0.1 + 0.2, 0.3) # True
# 算钱用 Decimal:
from decimal import Decimal
Decimal("0.1") + Decimal("0.2") # Decimal('0.3')float 是 IEEE 754 二进制,0.1 + 0.2 不等于 0.3。永远别用 == 比浮点,用 math.isclose(a, b)。算钱/要精确小数用 Decimal,且传字符串参数。
# ❌ 二维数组初始化的经典坑: grid = [[0] * 3] * 3 # 三行指向同一个 list! grid[0][0] = 1 print(grid) # [[1,0,0],[1,0,0],[1,0,0]] 全变了 # ✅ 用推导式各建各的: grid = [[0] * 3 for _ in range(3)] grid[0][0] = 1 # 只改第一行
[[0]*3]*3 是把同一个内层 list 引用重复 3 次,改一行全变。要用推导式 [[0]*3 for _ in range(3)] 各建各的。
1 and 2 # 2 and 返回最后一个真值(或第一个假值)
0 and 2 # 0
"" or "默认" # "默认" or 返回第一个真值(或最后一个值)
None or [] # []
# 实战:取默认值
name = user_input or "anonymous"
# 坑:当 0 / "" / [] 是合法值时会被当假值替换掉!
count = config.get("count") or 10 # count=0 也会变 10,应该用 get 默认值and/or 返回的是操作数本身不是布尔:a or b 是"a 真则 a 否则 b"。取默认值好用,但坑:合法的 0/""/[] 会被当假值替换掉,这种情况用 dict.get(k, 默认)。
d = {}
d.get("k") # None,不会创建键
d["k"] # KeyError,且不创建
# defaultdict 的坑:访问即创建!
from collections import defaultdict
dd = defaultdict(list)
_ = dd["x"] # 只是读,但 "x" 已被建出来了!
"x" in dd # True 意外地存在了
# 想读又不创建用 .get:
dd.get("y") # None,不创建defaultdict 用 [] 访问(哪怕只是读)就会创建键,_ = dd["x"] 会悄悄把 "x" 建出来。只想读不想建就用 .get()。
# ❌ Python 3 不能比较 int 和 str: sorted([1, "a", 2]) # TypeError [3, None, 1].sort() # TypeError (None 不能比) # ✅ 给 key 统一比较维度: sorted(mixed, key=str) # 都转字符串比 sorted(items, key=lambda x: (x is None, x)) # None 排最后
Python 3 不允许比较无关类型(int 和 str、任何东西和 None),排序混合列表会抛 TypeError。用 key= 把所有元素映射到同一可比维度。
a = "hello" b = "hello" a is b # 可能 True(小字符串驻留),但别依赖 x = "hello world!" y = "hello world!" x is y # 可能 False # 坑:== 才是值比较,is 是身份比较 # 只对 None / True / False 用 is: if val is None: ... # 永远别用 is 比字符串或数字内容
字符串驻留和小整数缓存是 CPython 实现细节,相等的字符串/整数用 is 比结果不可靠。is 只用于 None/True/False,比内容一律用 ==。
# ❌ list 不可哈希,不能当 dict 键或进 set:
{[1, 2]: "v"} # TypeError: unhashable type: 'list'
{[1, 2], [3, 4]} # TypeError
# ✅ 换成元组(不可变可哈希):
{(1, 2): "v"} # OK
{(1, 2), (3, 4)} # OK
# 自定义类当键要实现 __hash__ + __eq__,且哈希依赖的字段不能变只有可哈希(不可变)对象能当 dict 键或进 set,list/dict/set 会抛 unhashable type。换成元组;自定义类当键要实现 __hash__/__eq__,且哈希依赖的字段不能变。
# 模块只在第一次 import 时执行一次,结果被缓存:
# config.py
loaded_at = time.time() # 整个进程只算一次
cache = {} # 全模块共享,跨调用累积
# 坑:循环导入 / 改不到的"重新加载"
import importlib
importlib.reload(config) # 极少用,调试除外
# 别在模块顶层做重 IO / 网络请求,import 时就触发了模块顶层代码每进程只在首次 import 时跑一次,之后被缓存,模块级可变状态(缓存、列表)会跨所有调用方持续存在。别在模块顶层做重 IO/网络请求,import 时就触发了。
可搜索的 Python 速查表,覆盖日常真在撸的 100+ 段地道 代码,不是凑数的 hello-world 入门列表。十五大分类: 基础(赋值与拆包、int/float/Decimal、bool 真假值、 None 单例、isinstance、range),字符串(f-string 含 格式说明符、split/join、strip 系列、replace 与 re.sub、 切片、UTF-8 编解码),列表(append vs extend 坑、list 推导含过滤和三元、sorted 带 key 稳定排序、enumerate、 zip 与 strict、保序去重),字典(.get 链式取值、 setdefault vs defaultdict、3.9 | 合并、Counter),集 合(| & - ^ 做并交差对称差、frozenset 可哈希、推导式 去重),元组(单元素逗号坑、namedtuple、dataclass 与 field(default_factory)、*rest 解构),控制流(if/ elif、for-else、while、海象 :=、3.10 match/case 含 guard 和解构),函数(默认参数坑、lambda 限制、*args/ **kwargs 与 keyword-only、type hint、@decorator 配 functools.wraps、partial),类(__init__、@property setter、@classmethod / @staticmethod、dunder 方法、 super 与 MRO、@dataclass frozen + slots、__enter__/ __exit__),文件(open 必加 encoding="utf-8"、pathlib 的 / 拼接、json 必加 ensure_ascii=False、csv 必加 newline=""、tempfile),异常(try/except/else/finally、 raise from、自定义异常继承、contextlib.suppress),迭 代(next 带默认、yield 生成器、yield from、itertools chain/groupby/permutations/accumulate、functools.reduce / @lru_cache、zip_longest / takewhile),异步(asyncio. run、gather 加 return_exceptions、create_task/timeout、 async for / async with、Queue + Semaphore、3.11 TaskGroup),类型提示(3.9 list[int]、3.10 X | None、 TypedDict + NotRequired、Protocol 鸭子接口、TypeVar / Generic、Literal / Final、cast / @overload),以及 9 个真烧时间的坑(可变默认参数、late binding 闭包、浅 拷贝 vs 深拷贝、is vs ==、遍历时修改容器、 UnboundLocalError、裸 except 吞 Ctrl+C、-7 // 2 是 -4、循环里字符串拼接 O(n²))。每条都带:双语标题、 可直接复制的真实代码、双语说明、1-2 条变式。搜索框 跨标题 / 代码 / 说明 / 变式四字段一起过滤,分类胶囊 缩范围,一键复制。完全在浏览器里跑,不连任何服务、 不上传。配合 SQL / curl / git / regex 速查覆盖整条 技术栈,搭 JSON Formatter 处理数据。
把内容粘贴或拖入工具面板。
点击按钮,在浏览器内本地处理,文件不上传。
一键复制结果或下载到本地。
适合穿插在写代码、查问题、做 Review、上线前的小任务里。
这些入口会把当前任务接到更完整的工具链里。
把 JSON 解析进字典时,忘了该用 setdefault 还是 defaultdict。搜索框输 setdefault,字典分类的条目带可复制代码、说明和变式一起出来,直接抄对那条,不用切出编辑器去翻整页文档。
要发 1000 个 HTTP 请求前,筛到异步分类,看 asyncio.gather 配 Semaphore 的条目。每条都给出确切调用和 3.11 TaskGroup 变式,你按解释器版本挑对那一份再上线。
搜类名比如 Counter 没搜到,因为输成了 counter,代码字段区分大小写,散文词试试计数、去重这种小写关键词。
把 3.10 的 match-case 片段抄到 3.8 解释器上;先看版本标签,用旁边给的旧版兜底写法。
以为搜索只匹配标题;其实标题、代码、说明、变式四个字段一起过滤,词藏在代码变式里那条也会出来。
全部在你浏览器里跑。整份速查是单个静态页,搜索只对内存里的片段数组做过滤,不执行 Python,不上传,零网络请求。公司代理后面、气隙机器上都能放心用。
做你这行的人, 还会一起用这些。