跳到主要内容

Python 速查表:100+ 段地道 Python 代码,含异步、类型提示与常见坑

Python 速查表,100+ 段地道 Python 代码片段,涵盖字符串/列表/字典/文件/异步,带真实例子。

  • 本地处理
  • 分类 开发运维
  • 适合 格式化、校验、压缩或检查和代码相关的文本。
170
基础 (11)

变量赋值与多重赋值

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]

数值类型 int / float / complex / decimal

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.3

int 任意精度(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 与真假值判断

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

None 单例

x = None
if x is None:        # ✅ 推荐
    print("空值")
if x == None:        # 能跑但不地道
    pass

None 是单例,必须用 is None / is not None 判断,不要写 == None。is 比较对象身份,== 是值相等,可被重载坑掉你。

用 isinstance 判断类型

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 与 input

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 区间序列

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 / abs / pow / divmod

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 同时给商和余。

chr / ord ASCII 与 Unicode

ord("A")     # 65
chr(65)      # 'A'
ord("中")    # 20013
chr(0x1F600) # '😀'
"A" < "B"    # True   按码点比较
[chr(c) for c in range(97, 123)]   # a-z

ord() 拿字符的 Unicode 码点,chr() 反向。字符串比较按码点。chr(0x1F600) 用十六进制码点拼出 emoji。

字符串 (17)

f-string(现代格式化首选)

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}"   # 右对齐宽 10
f"{x:0>5}"   # 左补 0 到 5 位
f"{x:.0%}"   # 百分比

.format() 和 % 老写法

"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。

split / join 字符串与列表互转

"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 次数)

strip / lstrip / rstrip 去空白

"  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"

replace 字符串替换

"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() 返回新字符串,字符串不可变。

startswith / endswith / in

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 / decode

"你好".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" 让坏字节不抛异常。

多行字符串与 textwrap

s = """第一行
第二行
第三行"""
import textwrap
textwrap.dedent("""
    缩进的多行字符串
    自动去掉前导空格
""")
textwrap.fill("超长一段话", width=40)

三引号字符串保留换行。textwrap.dedent 去掉每行共同的前导空白,内嵌 SQL/模板特别好用。

find / index / count 子串定位

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")        # 5

find() 返回索引,找不到给 -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)等价行内写法。

splitlines / partition

"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 按第一个分隔切成三元组,找不到也不抛错,返回空段。

str 判定方法

"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()。

str.translate 批量字符映射

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() 快。

正则 re 常用

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)    # 正则替换

bytes 与 hex / base64

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。

列表 (18)

append / extend / insert

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[:] 是浅拷贝的经典写法。切片赋值是原地修改,批量替换很方便。

list 推导式

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 循环吧,可读性优先

map / filter 函数式写法

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 / sort / key / reverse

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 是稳定排序。

reverse / reversed

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 有序,可放心用)。

enumerate 同时拿索引和值

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= 改起始索引。

zip 多个可迭代并行遍历

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 / any 列表逻辑判断

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 / min / max 聚合

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= 不抛。

pop / remove / del

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 是语句不是表达式,没返回值。

index / count 查找与计数

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) 数出现次数。

bisect 有序列表二分

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 保持有序插入。经典用法:把分数映射到等级区间。

heapq 堆 / 优先队列

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 比整体排序快。要大顶堆就存负数。

collections.deque 双端队列

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 写递归生成器。

列表分块 chunk

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),对任意可迭代都行,不限于可切片的列表。

字典 (12)

dict 基本操作

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 保证插入顺序,这是语言规范,不是实现细节,可放心依赖。

dict.get 安全取值

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(...) 是常见安全写法。

dict.setdefault 一次性"取 or 初始化"

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) 是更优雅的分组写法。

dict.update 合并字典

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 返回新字典。冲突时后者覆盖前者。

dict 推导式

{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、临时建查表。

collections.Counter

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 个。支持 + - & | 做多重集合运算。

collections.defaultdict

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))   # 嵌套 dict

defaultdict 首次访问就自动创建默认值。defaultdict(int) 计数,defaultdict(list) 分组,lambda 套娃做嵌套。

OrderedDict(3.7 后基本可弃用)

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) 才有必要保留。

dict.pop / popitem / clear

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() 原地清空。

dict 视图与集合运算

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() 直接支持集合运算符(& | -),比对两个字典差异很方便。

collections.ChainMap 多字典叠加

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)       # 需要时再拍平成普通 dict

ChainMap 按顺序在多个字典里查,不复制不合并,第一个命中的赢。做分层配置(命令行 > 环境 > 文件 > 默认)特别合适。

dict 按值排序 / 取 TopN

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。

集合 (7)

set 创建与基本操作

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()。

set 交并差

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() 等方法名版,可读性上运算符赢。

frozenset 可哈希集合

fs = frozenset([1, 2, 3])
# 可以做 dict 的键、放进 set 里
d = {fs: "value"}
s = {frozenset([1, 2]), frozenset([3, 4])}

frozenset 不可变可哈希,能当 dict 的 key、能放进 set 里。普通 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:。算"去重总数"一行搞定。

set 原地更新

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 无序)。

用 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 是删除,& 是保留。注意会丢顺序和重复,只看存在性。

元组 / dataclass (7)

tuple 基础与不可变性

t = (1, 2, 3)
t = 1, 2, 3            # 括号可省,逗号才是关键
single = (1,)          # 单元素元组要加逗号!
empty = ()
# 不可变,不能改:
# t[0] = 99  → TypeError
a, b, c = t            # 拆包

元组靠逗号定义,不是括号。单元素元组要写 (1,),(1) 只是数字 1,最常见的坑。

namedtuple 轻量数据结构

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。

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: float

dataclass(3.7+)自动生成 __init__ / __repr__ / __eq__。可变默认值必须 field(default_factory=list),否则中招"可变默认参数"坑。frozen=True 出不可变版本。

tuple 解构与 *rest

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 能接中间或末尾。函数返回多值其实就是返回元组,调用端再解构出来。

NamedTuple 类语法 + 默认值

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() 返回修改后的新元组(元组不可变)。比函数式写法更现代。

dataclass 进阶

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 配套工具。

enum.Enum 枚举

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,成员直接当字符串/整数用。

控制流 (8)

if / elif / else

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 / for-else

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, 8

for-else:循环没被 break 才会跑 else,写"找不到就报告"的逻辑很简洁。

while / break / continue

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。

match / case(3.10+ 模式匹配)

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。

walrus := 海象运算符

# 在 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]

海象 := 既赋值又返回。最适合"循环条件里要拿值又要判"和"推导式里要复用计算结果"两种场景。

match 进阶解构

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 金字塔拍平,边界情况先处理掉,主逻辑保持最浅缩进,可读性大涨。

函数 (13)

def 定义函数 / 默认参数

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 然后在函数里初始化。

lambda 匿名函数

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。

*args 与 **kwargs

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 设计利器,避免位置参数搞混。

type hint 类型注解

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。

装饰器 decorator

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 坑

# 经典 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,用默认参数把当前值固定下来。

global 与 nonlocal

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。

partial 预绑定参数

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) 先调最外层工厂拿到装饰器,再去装饰函数。

functools.singledispatch

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 判断干净。

聚合:reduce vs 内置

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 r

Python 不做尾递归优化,深递归会触发 RecursionError(约 1000 层)。热点递归改写成迭代;调大递归限制是下策,可能直接栈溢出崩。

(13)

class 基础 / __init__ / self

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 是当前实例,约定俗成,但每个方法定义都必须显式带上。

@property 优雅封装

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 函数地道。

@classmethod / @staticmethod

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 啥都不接收,是放在类命名空间里的工具函数。

dunder methods 让对象像内置类型

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__ 让 + 工作。

继承与 super

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(方法解析顺序)。

@dataclass + frozen + slots

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__ 省内存,且能防止打错字新建属性。

自定义 with 上下文

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__ 才稳。

类属性 vs 实例属性

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 = [])会被所有实例共用。

__slots__ 省内存

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。代价是不能再动态加属性。

abc.ABC 抽象基类

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。

__call__ 让实例可调用

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__ 都不用写。

functools.total_ordering

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 自动补全其余比较运算符,全序类型少写一堆样板。

文件 / IO (10)

open 与 with 读写文件

# 读文本(自动指定 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 害死人)。

pathlib 现代路径操作

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。

json 读写 JSON

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 转义。

csv 读写 CSV

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 自动处理表头。

tempfile 临时文件 / 目录

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 自动清理,省心。

os / shutil 文件系统操作

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。

glob 通配符找文件

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 是更现代的等价写法。

pickle 对象序列化

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。

configparser 读 INI

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)用起来像嵌套字典。

gzip / zipfile 压缩

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 解包。

异常 (8)

try / except / else / finally

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 抛异常 / 链式异常

raise ValueError("age 不能为负")
raise ValueError(f"非法值 {x}")

# 包装别人的异常(保留原始 traceback)
try:
    int(s)
except ValueError as e:
    raise ParseError("解析失败") from e

# 想隐藏原始链:
raise ParseError("...") from None

raise 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。

contextlib.suppress 优雅忽略

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 干净。

assert 断言

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,别用它做线上输入校验,它只是开发期的不变量检查。

finally 与资源清理

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,还会吞掉异常。

logging.exception 记录堆栈

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()。

ExceptionGroup(3.11+)

# 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* 在组里分别匹配并处理每种类型。

迭代器 / 生成器 (10)

iter / next 迭代器协议

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.i

iter(obj) 拿迭代器,next(it) 推进。next(it, 默认) 安全版避免 StopIteration。for 循环背后就是在调这两个。

generator / yield 惰性生成

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 让函数变生成器,惰性产值、状态保留。生成器表达式用 () 不是 [],处理超大数据时省内存。

yield from 委托子生成器

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 简洁。

itertools 常用

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 排序。

functools.reduce / lru_cache

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) 的简写。

zip_longest / takewhile / dropwhile

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]      跳到第一个 False

zip_longest 用 fillvalue 补齐短的一边。takewhile / dropwhile 在第一个不满足条件处切开,处理流式数据特别合适。

itertools 无限迭代器

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 把序列无限循环。

itertools starmap / pairwise

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 个独立的。

generator.send / close

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 关闭它。

enumerate + zip 组合

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))。

异步 (8)

async / await 基础

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,会报错。

asyncio.gather 并发执行

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 是更安全的现代写法,异常处理更干净。

asyncio.create_task / wait / timeout

# 立刻调度,不等:
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 for / async with

# 异步迭代器
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 客户端、数据库驱动、流文件常见。

asyncio.Queue / Semaphore 并发原语

# 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 当哨兵告诉消费者结束。

asyncio.to_thread 跑阻塞代码

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。

async 生成器 / 上下文

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 消费。

asyncio.run 与事件循环

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 即可。

类型提示 (11)

typing 基础

# 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)还能用但不必。

Optional / Union / None

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 替代,更短。

TypedDict 结构化字典

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 报错:缺 age

TypedDict 给字典形状加类型,JSON 风格数据特别适合,不用写整个类。3.11+ 的 NotRequired / Required 支持可选键。

Protocol 鸭子类型 + 静态检查

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。

Generic / TypeVar 泛型

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])。

Literal / Final / Annotated

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 会读。

cast / overload 类型断言与重载

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 声明多重签名(一份实现),适合返回类型随入参类型变的函数。

Callable / ParamSpec 函数类型

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 inner

Callable[[参数类型], 返回] 给函数加类型;Callable[..., R] 接受任意参数。ParamSpec(3.10+)让装饰器在类型检查时保留被包函数的精确签名。

TYPE_CHECKING 避免循环导入

from __future__ import annotations   # 注解延迟求值
from typing import TYPE_CHECKING
if TYPE_CHECKING:
    from .models import User      # 只在类型检查时导入

def greet(u: User) -> str:        # 字符串注解,运行时不真导入
    return u.name

TYPE_CHECKING 运行时为 False、类型检查时为 True,把只为类型用的 import 放进去,打破循环导入。配 from __future__ import annotations 用。

Self / assert_type(3.11+)

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 运行时啥都不做,让类型检查器验证推断出的类型。

NewType 语义化类型

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)。零运行时开销。

常见坑 (17)

可变默认参数

# ❌ 错误(所有调用共享同一个 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 哨兵 + 函数内初始化。

late binding 闭包

# ❌ 全是 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: 把当前值冻在默认参数里。

浅拷贝 vs 深拷贝

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() 递归克隆整棵树。要不要深拷贝看内部对象会不会被修改。

is vs ==

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() 拷一份再遍历,要么用推导式新建一个。

global / nonlocal 不写就报错

count = 0
def inc():
    count += 1            # ❌ UnboundLocalError
                          # 因为函数里赋值了 count,Python 把它当本地变量

def inc():
    global count          # ✅ 显式声明
    count += 1

函数里任何地方给 count 赋值,Python 就认它是本地变量(哪怕在赋值前用也算),不写 global / nonlocal 就 UnboundLocalError。

整数除法 / vs //

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),大数据量差距巨大。

裸 except 是坑

# ❌ 吞掉 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)] 各建各的。

and / or 返回值不是布尔

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, 默认)。

in 判断与 KeyError 时机

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()。

sort 混合类型报错

# ❌ 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= 把所有元素映射到同一可比维度。

is 比较字符串数字陷阱

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 处理数据。

工具细节

输入
文本
页面会根据工具类型展示文本框、数值控件、文件选择或结构化输入。
输出
即时结果 + 复制
结果区优先给出可操作结果,支持项会显示复制、下载或可视化预览。
隐私
可能使用网络查询
组件源码里检测到网络调用,页面会按工具逻辑处理;敏感内容建议先脱敏。
保存 / 分享
免账号使用
打开页面即可使用;刷新后是否保留结果取决于具体工具。
性能预算
首屏 JS ≤ 30 KB
没有声明 WASM 依赖,适合快速打开和移动端使用。
适用场景
开发运维 · 程序员
分类和职业标签用于推荐相关工具、组织内链,并帮助用户快速判断是否适合当前任务。

怎么用

  1. 1. 输入

    把内容粘贴或拖入工具面板。

  2. 2. 处理

    点击按钮,在浏览器内本地处理,文件不上传。

  3. 3. 复制 / 下载

    一键复制结果或下载到本地。

Python 速查表 适合怎么用

适合穿插在写代码、查问题、做 Review、上线前的小任务里。

适合开发场景

  • 格式化、校验、压缩或检查和代码相关的文本。
  • 把片段整理好再放进文档、工单、提交或交接材料。
  • 不切换工具,快速检查一个小 payload。

开发检查项

  • 压缩、混淆这类不可逆处理,先对副本操作。
  • 除非确认工具本地处理,不要粘贴密钥和敏感片段。
  • 转换后的代码上线前,仍要跑自己的测试或 lint。

下一步可以接着做

这些入口会把当前任务接到更完整的工具链里。

  1. 1 JSON 格式化与校验 浏览器内即时格式化、校验、压缩 JSON,数据不离开本地。 打开
  2. 2 SQL 速查表 SQL 速查表,100+ 条覆盖 SELECT、JOIN、窗口函数、索引,含 MySQL/PostgreSQL/SQLite 方言差异。 打开
  3. 3 curl 命令速查 curl 命令速查,80+ 条覆盖 GET/POST/认证/上传/下载/SSL/代理,真实例子和常见坑。 打开

真实使用场景

  • 撸代码撸到一半,想不起 dict.get 怎么写

    把 JSON 解析进字典时,忘了该用 setdefault 还是 defaultdict。搜索框输 setdefault,字典分类的条目带可复制代码、说明和变式一起出来,直接抄对那条,不用切出编辑器去翻整页文档。

  • 上线并发请求前对一遍异步写法

    要发 1000 个 HTTP 请求前,筛到异步分类,看 asyncio.gather 配 Semaphore 的条目。每条都给出确切调用和 3.11 TaskGroup 变式,你按解释器版本挑对那一份再上线。

常见踩坑

  • 搜类名比如 Counter 没搜到,因为输成了 counter,代码字段区分大小写,散文词试试计数、去重这种小写关键词。

  • 把 3.10 的 match-case 片段抄到 3.8 解释器上;先看版本标签,用旁边给的旧版兜底写法。

  • 以为搜索只匹配标题;其实标题、代码、说明、变式四个字段一起过滤,词藏在代码变式里那条也会出来。

隐私说明

全部在你浏览器里跑。整份速查是单个静态页,搜索只对内存里的片段数组做过滤,不执行 Python,不上传,零网络请求。公司代理后面、气隙机器上都能放心用。

常见问题

类似工具组合

做你这行的人, 还会一起用这些。

Made by Toolora · 100% client-side · Updated 2026-07-02