附录 A Python 速查表
这份表给写过别的语言、要快速想起 Python 怎么写的人:按节查,不从头读。每个条目是一行说明加一段代码,只讲怎么写,不讲为什么;需要原理的条目在说明末尾注明正文章节。代码以 Python 3.12 为准,每段都能直接运行,注释里写的是运行结果;标“第三方”的工具需要另外安装。
官方下载地址:https://www.python.org/downloads/
查看版本
python --version在代码里判断版本
import sys
print(sys.version_info >= (3, 12)) # True查看当前用的是哪个解释器
import sys
print(sys.executable)安装 uv(第三方,管理 Python 版本、虚拟环境和依赖)
# macOS / Linuxcurl -LsSf https://astral.sh/uv/install.sh | sh# Windows(PowerShell)powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"用 uv 安装指定版本的 Python
uv python install 3.12列出已安装和可安装的 Python 版本
uv python list新建虚拟环境(默认目录 .venv)并指定版本
uv venv --python 3.12激活虚拟环境
source .venv/bin/activate # macOS / Linux.venv\Scripts\activate # Windows运行单个文件
python hello.py进入交互式环境(REPL),输入 exit 退出
python运行文件后留在 REPL 里,可以查看变量
python -i hello.py直接执行一行代码
python -c "print(2 ** 10)"把标准库或已安装的模块当脚本运行
python -m http.server 8000查看 Python 编译出的字节码,详见第 2 章
import dis
dis.dis(lambda x: x + 1)打包成单个可执行文件:用 PyInstaller(第三方)
pyinstaller --onefile hello.py读取命令行参数,sys.argv[0] 是脚本名
import sys
# 运行:python args.py a bprint(sys.argv[1:]) # ['a', 'b']用 argparse 解析参数,自带 --help
import argparse
parser = argparse.ArgumentParser(description="打招呼")parser.add_argument("name")parser.add_argument("--times", type=int, default=1)args = parser.parse_args()for _ in range(args.times): print(f"你好,{args.name}")# 运行:python greet.py 小明 --times 2读环境变量,不存在时给默认值
import os
print(os.environ.get("APP_MODE", "dev")) # dev以非零退出码结束程序,字符串参数会打印到标准错误
import sys
sys.exit("配置文件不存在") # 退出码 1项目初始化与依赖管理
Section titled “项目初始化与依赖管理”新建项目,生成 pyproject.toml、main.py 等文件(uv,第三方,下同)
uv init democd demo添加依赖,同时更新 pyproject.toml 和 uv.lock
uv add requests添加只在开发时用的依赖
uv add --dev pytest移除依赖
uv remove requestspyproject.toml:项目名、版本、Python 要求、依赖和开发依赖(节选)
[project]name = "demo"version = "0.1.0"requires-python = ">=3.12"dependencies = [ "requests>=2.32.3",]
[dependency-groups]dev = [ "pytest>=8.3.5",]锁文件 uv.lock 由 uv 维护,提交进版本库,不要手改;依赖变了重新生成
uv lock按锁文件把依赖装进 .venv(拿到别人的项目先跑这个)
uv sync在项目的虚拟环境里运行脚本,不用先激活
uv run main.py不用 uv 的传统方式:建虚拟环境
python -m venv .venv不用 uv 的传统方式:激活虚拟环境后装包
python -m pip install requests赋值即声明,不需要关键字;同一个名字可以换成别的类型
count = 0count = "零"print(count) # 零带类型标注的变量(只给类型检查器看,运行时不检查),详见第 3 章
name: str = "Ada"print(name) # Ada多重赋值
x, y = 1, 2print(x, y) # 1 2交换两个变量
a, b = 1, 2a, b = b, aprint(a, b) # 2 1常量靠全大写命名约定,语言不阻止修改
MAX_RETRIES = 3print(MAX_RETRIES) # 3用 Final 标注常量,类型检查器会报告重新赋值,运行时不拦,详见第 4 章
from typing import Final
RATE: Final = 0.05print(RATE) # 0.05作用域只分函数和模块:函数里赋值的名字是局部变量,读取时按 局部 → 外层函数 → 模块 → 内置 的顺序查找
greeting = "你好"
def say() -> None: print(greeting) # 没有赋值,读到模块级的变量
say() # 你好在函数里修改模块级变量要声明 global
counter = 0
def bump() -> None: global counter counter += 1
bump()print(counter) # 1没有块级作用域:for、if 里定义的变量在块外仍然可用
for i in range(3): last = i * 10print(i, last) # 2 20删除一个名字
x = 1del xprint("x" in globals()) # False基本类型与转换
Section titled “基本类型与转换”字符串转整数,可指定进制
print(int("42")) # 42print(int("ff", 16)) # 255字符串转浮点数,浮点数转整数(向零截断)
print(float("3.14")) # 3.14print(int(-3.9)) # -3任意值转字符串
print(str(42) + str(3.5) + str(True)) # 423.5True转布尔值:0、0.0、空串、空容器、None 为假,其余为真
print(bool(0), bool(""), bool([]), bool("0")) # False False False True/ 总是得浮点数,// 向下取整,% 取余,** 乘方
print(7 / 2, 7 // 2, -7 // 2, 7 % 3, 2 ** 10) # 3.5 3 -4 1 1024同时得商和余数
print(divmod(17, 5)) # (3, 2)整数没有上限,不会溢出
print(2 ** 100) # 1267650600228229401496703205376浮点数有误差,比较用 math.isclose,算钱用 Decimal
import mathfrom decimal import Decimal
print(0.1 + 0.2) # 0.30000000000000004print(math.isclose(0.1 + 0.2, 0.3)) # Trueprint(Decimal("0.1") + Decimal("0.2")) # 0.3round 遇到 .5 取最近的偶数
print(round(2.5), round(3.5), round(3.14159, 2)) # 2 4 3.14判断类型用 isinstance;bool 是 int 的子类
print(isinstance(3, int), isinstance(3, (int, float)), isinstance(True, int)) # True True True数字字面量:下划线分隔、十六进制、二进制
print(1_000_000, 0xFF, 0b1010) # 1000000 255 10插值用 f-string
name, age = "小明", 18print(f"{name} 今年 {age} 岁") # 小明 今年 18 岁f-string 的格式说明:小数位、千分位、百分比、宽度和对齐
pi, n = 3.14159, 1234567print(f"{pi:.2f}|{n:,}|{0.25:.0%}|{'右':>4}|{42:05d}") # 3.14|1,234,567|25%| 右|00042!r 用 repr 显示(字符串带引号),= 连表达式一起打印,调试常用
s = "hi"print(f"{s!r}", f"{len(s) = }") # 'hi' len(s) = 2拼接少量用 +,拼接一列用 join
print("a" + "b", "-".join(["2026", "10", "07"])) # ab 2026-10-07切片:[开始:结束:步长],不含结束位置,负数从末尾数
s = "Python"print(s[0], s[-1], s[1:4], s[::-1]) # P n yth nohtyP查找:in 判断包含,find 找不到返回 -1
s = "hello world"print("wor" in s, s.find("o"), s.find("z"), s.startswith("he")) # True 4 -1 True替换
print("a-b-c".replace("-", "/")) # a/b/c分割:不带参数按任意空白分割,带参数按指定分隔符
print("a b\tc".split(), "x,y,,z".split(",")) # ['a', 'b', 'c'] ['x', 'y', '', 'z']大小写
s = "hello World"print(s.upper(), s.lower(), s.title()) # HELLO WORLD hello world Hello World去掉两端空白,或只去左边、右边
s = " hi \n"print(repr(s.strip()), repr(s.lstrip()), repr(s.rstrip())) # 'hi' 'hi \n' ' hi'多行字符串用三引号
sql = """SELECT *FROM users""".strip()print(sql.splitlines()) # ['SELECT *', 'FROM users']原始字符串:反斜杠不转义,写 Windows 路径和正则用
print(r"C:\new\table") # C:\new\tablelen 数的是码点,不是字节,详见第 14 章
s = "中😀"print(len(s), len(s.encode("utf-8"))) # 2 7字符串和字节互转
data = "你好".encode("utf-8")print(data, data.decode("utf-8")) # b'\xe4\xbd\xa0\xe5\xa5\xbd' 你好字符串不可变:要改就拼一个新的
s = "hello"s = "J" + s[1:]print(s) # Jello列表:创建、追加、插入、按值删除、弹出末尾
nums = [3, 1, 2]nums.append(4)nums.insert(0, 0)nums.remove(3)last = nums.pop()print(nums, last) # [0, 1, 2] 4列表:索引、修改、长度、包含、切片
nums = [10, 20, 30, 40]nums[0] = 11print(nums[-1], len(nums), 20 in nums, nums[1:3]) # 40 4 True [20, 30]排序:sorted 返回新列表,list.sort 原地排;key 指定按什么排
words = ["banana", "Apple", "cherry"]print(sorted(words, key=str.lower)) # ['Apple', 'banana', 'cherry']words.sort(key=len, reverse=True)print(words) # ['banana', 'cherry', 'Apple']字典:创建、增改、读取、删除
ages = {"小明": 18, "小红": 20}ages["小刚"] = 19ages["小明"] = 17del ages["小红"]print(ages["小明"], ages) # 17 {'小明': 17, '小刚': 19}字典:键不存在时 [] 报 KeyError,get 返回默认值
ages = {"小明": 18}print(ages.get("小红"), ages.get("小红", 0)) # None 0遍历字典的键值对(按插入顺序)
ages = {"小明": 18, "小红": 20}for name, age in ages.items(): print(name, age)setdefault:键不存在就先放一个默认值,再返回它
groups: dict[str, list[str]] = {}groups.setdefault("水果", []).append("苹果")groups.setdefault("水果", []).append("梨")print(groups) # {'水果': ['苹果', '梨']}defaultdict:读不存在的键时自动创建默认值
from collections import defaultdict
by_len: defaultdict[int, list[str]] = defaultdict(list)for w in ["a", "bb", "cc", "d"]: by_len[len(w)].append(w)print(dict(by_len)) # {1: ['a', 'd'], 2: ['bb', 'cc']}Counter:计数
from collections import Counter
c = Counter("mississippi")print(c["s"], c.most_common(2)) # 4 [('i', 4), ('s', 4)]集合:去重、并、交、差
a, b = {1, 2, 3}, {2, 3, 4}a.add(5)print(a | b, a & b, a - b) # {1, 2, 3, 4, 5} {2, 3} {1, 5}元组:不可变,可以当字典的键
point = (3, 4)distance = {point: 5.0}print(point[0], distance[(3, 4)]) # 3 5.0推导式:列表、字典、集合
nums = [1, 2, 3, 4]print([n * n for n in nums if n % 2 == 0]) # [4, 16]print({n: n * n for n in nums}) # {1: 1, 2: 4, 3: 9, 4: 16}print({n % 3 for n in nums}) # {0, 1, 2}解包:* 展开序列,** 展开字典,*名字 收集剩余元素
a, b = [1, 2], [3]defaults, overrides = {"debug": False, "port": 80}, {"port": 8080}first, *rest = [*a, *b]print(first, rest, {**defaults, **overrides}) # 1 [2, 3] {'debug': False, 'port': 8080}求和、最大、最小
nums = [3, 1, 4]print(sum(nums), max(nums), min(nums)) # 8 4 1if / elif / else,条件不加括号,代码块靠缩进
score = 85if score >= 90: grade = "A"elif score >= 80: grade = "B"else: grade = "C"print(grade) # B三元表达式
n = 7print("奇数" if n % 2 else "偶数") # 奇数链式比较
x = 5print(0 < x < 10) # Truefor 遍历任何可迭代对象;数字范围用 range(不含结束值)
for i in range(0, 10, 3): print(i) # 依次打印 0 3 6 9同时要下标和元素用 enumerate
for i, ch in enumerate("ab", start=1): print(i, ch) # 依次打印 1 a、2 b并排遍历几个序列用 zip,按最短的停
for name, age in zip(["小明", "小红"], [18, 20]): print(name, age)while、break、continue
n = 0while True: n += 1 if n % 2 == 0: continue if n > 5: break print(n) # 依次打印 1 3 5for … else:循环没被 break 打断才执行 else
for n in [3, 5, 7]: if n % 2 == 0: print("找到偶数", n) breakelse: print("没有偶数") # 没有偶数match 匹配字面量,| 表示或,_ 兜底(3.10+)
code = 404match code: case 200 | 201: print("成功") case 404: print("找不到") # 找不到 case _: print("其他")match 按结构匹配:序列、字典、类,可以加 if 守卫,详见第 15 章
def handle(event: object) -> str: match event: case {"type": "click", "x": x, "y": y}: return f"点击 ({x}, {y})" case ["move", dx, dy] if dx or dy: return f"移动 {dx},{dy}" case str() as text: return f"文本 {text}" case _: return "未知"
print(handle({"type": "click", "x": 1, "y": 2})) # 点击 (1, 2)print(handle(["move", 3, 0])) # 移动 3,0print(handle("hi")) # 文本 hi海象运算符 :=:在表达式里赋值
items = [1, 2, 3, 4]if (n := len(items)) > 3: print(f"太长了:{n} 个") # 太长了:4 个pass 占位,什么都不做
def todo() -> None: pass
print(todo()) # None定义函数,参数和返回值带类型标注,参数可以有默认值
def greet(name: str, greeting: str = "你好") -> str: return f"{greeting},{name}"
print(greet("小明")) # 你好,小明print(greet("小明", greeting="早上好")) # 早上好,小明默认值只在定义时求值一次:可变默认值用 None 代替,详见第 7 章
def add_item(item: str, items: list[str] | None = None) -> list[str]: if items is None: items = [] items.append(item) return items
print(add_item("a"), add_item("b")) # ['a'] ['b']*args 收集任意多个位置参数(得到元组)
def total(*nums: int) -> int: return sum(nums)
print(total(1, 2, 3)) # 6**kwargs 收集任意多个关键字参数(得到字典)
def show(**options: object) -> None: for key, value in options.items(): print(key, value)
show(color="red", size=3)* 之后的参数只能按关键字传
def connect(host: str, *, timeout: float = 5.0) -> str: return f"{host} 超时 {timeout}"
print(connect("example.com", timeout=2)) # example.com 超时 2/ 之前的参数只能按位置传
def power(base: float, exp: float, /) -> float: return base ** exp
print(power(2, 3)) # 8调用时用 *、** 把序列和字典展开成参数
def point(x: int, y: int) -> str: return f"({x}, {y})"
print(point(*[1, 2]), point(**{"x": 3, "y": 4})) # (1, 2) (3, 4)返回多个值:返回元组,调用方解包
def min_max(nums: list[int]) -> tuple[int, int]: return min(nums), max(nums)
lo, hi = min_max([3, 1, 4])print(lo, hi) # 1 4匿名函数 lambda:只能写一个表达式
pairs = [("b", 2), ("a", 3), ("c", 1)]print(sorted(pairs, key=lambda p: p[1])) # [('c', 1), ('b', 2), ('a', 3)]闭包:内层函数记住外层变量,修改它要声明 nonlocal,详见第 7 章
def make_counter(): count = 0 def inc() -> int: nonlocal count count += 1 return count return inc
c = make_counter()c()print(c()) # 2函数是普通的值,可以传入、返回、放进容器
def apply(f, x: int) -> int: return f(x)
ops = {"double": lambda x: x * 2, "neg": lambda x: -x}print(apply(ops["double"], 5), apply(abs, -3)) # 10 3装饰器:用一个函数包装另一个函数,详见第 22 章
import functools
def logged(func): @functools.wraps(func) def wrapper(*args, **kwargs): print(f"调用 {func.__name__}{args}") return func(*args, **kwargs) return wrapper
@loggeddef add(a: int, b: int) -> int: return a + b
print(add(1, 2)) # 先打印"调用 add(1, 2)",再打印 3定义类:__init__ 是构造方法,方法第一个参数是 self
class Account: def __init__(self, owner: str, balance: float = 0) -> None: self.owner = owner self.balance = balance
def deposit(self, amount: float) -> None: self.balance += amount
acc = Account("小明")acc.deposit(100)print(acc.owner, acc.balance) # 小明 100字符串表示:__repr__ 给开发者看,__str__ 给 print 用
class Point: def __init__(self, x: int, y: int) -> None: self.x, self.y = x, y
def __repr__(self) -> str: return f"Point({self.x}, {self.y})"
def __str__(self) -> str: return f"({self.x}, {self.y})"
p = Point(1, 2)print(p, repr(p)) # (1, 2) Point(1, 2)@dataclass 自动生成 __init__、__repr__、__eq__;可变默认值用 field(default_factory=…)
from dataclasses import dataclass, field
@dataclassclass User: name: str age: int = 0 tags: list[str] = field(default_factory=list)
u = User("小明", 18)print(u, u == User("小明", 18)) # User(name='小明', age=18, tags=[]) True不可变的数据类:frozen=True,可以当字典键
from dataclasses import dataclass
@dataclass(frozen=True)class Point: x: int y: int
print({Point(1, 2): "A"}[Point(1, 2)]) # A@property:像属性一样读写,背后是方法(可做校验)
class Temperature: def __init__(self, celsius: float) -> None: self.celsius = celsius
@property def celsius(self) -> float: return self._celsius
@celsius.setter def celsius(self, value: float) -> None: if value < -273.15: raise ValueError("低于绝对零度") self._celsius = value
@property def fahrenheit(self) -> float: return self._celsius * 9 / 5 + 32
t = Temperature(100)print(t.fahrenheit) # 212.0继承,用 super() 调父类方法
class Animal: def __init__(self, name: str) -> None: self.name = name
def speak(self) -> str: return "..."
class Dog(Animal): def __init__(self, name: str, breed: str) -> None: super().__init__(name) self.breed = breed
def speak(self) -> str: return f"{self.name}:汪"
d = Dog("旺财", "柴犬")print(d.speak(), isinstance(d, Animal)) # 旺财:汪 True自定义相等:实现 __eq__,类型不对返回 NotImplemented;要放进集合或当键还得实现 __hash__,详见第 5 章
class Money: def __init__(self, cents: int) -> None: self.cents = cents
def __eq__(self, other: object) -> bool: if not isinstance(other, Money): return NotImplemented return self.cents == other.cents
def __hash__(self) -> int: return hash(self.cents)
print(Money(100) == Money(100), Money(100) == 100) # True False类方法 @classmethod:第一个参数是类本身,常用作备用构造方法
from datetime import date
class Person: def __init__(self, name: str, age: int) -> None: self.name, self.age = name, age
@classmethod def from_birth_year(cls, name: str, year: int) -> "Person": return cls(name, date.today().year - year)
p = Person.from_birth_year("小明", 2000)print(p.name) # 小明静态方法 @staticmethod:不需要 self 也不需要类
class MathUtil: @staticmethod def clamp(x: float, lo: float, hi: float) -> float: return max(lo, min(x, hi))
print(MathUtil.clamp(15, 0, 10)) # 10类属性被所有实例共享,实例属性各自一份
class Dog: species = "犬" # 类属性
def __init__(self, name: str) -> None: self.name = name # 实例属性
print(Dog("a").species, Dog.species) # 犬 犬枚举,详见第 15 章
from enum import Enum
class Color(Enum): RED = 1 GREEN = 2
print(Color.RED, Color(2).name, [c.name for c in Color]) # Color.RED GREEN ['RED', 'GREEN']Protocol 定义结构化接口:有这些方法就算实现,不用继承,详见第 8 章
from typing import Protocol
class Shape(Protocol): def area(self) -> float: ...
class Square: # 没有继承 Shape def __init__(self, side: float) -> None: self.side = side
def area(self) -> float: return self.side ** 2
def total_area(shapes: list[Shape]) -> float: return sum(s.area() for s in shapes)
print(total_area([Square(2), Square(3)])) # 13加 @runtime_checkable 后可以用 isinstance 检查(只看方法名在不在)
from typing import Protocol, runtime_checkable
@runtime_checkableclass Closable(Protocol): def close(self) -> None: ...
class Conn: def close(self) -> None: print("关闭")
print(isinstance(Conn(), Closable), isinstance(42, Closable)) # True FalseABC 加 @abstractmethod 定义名义接口:子类必须显式继承
from abc import ABC, abstractmethod
class Storage(ABC): @abstractmethod def save(self, key: str, value: str) -> None: ...
def save_all(self, items: dict[str, str]) -> None: # 可以带普通方法 for k, v in items.items(): self.save(k, v)
class MemoryStorage(Storage): def __init__(self) -> None: self.data: dict[str, str] = {}
def save(self, key: str, value: str) -> None: self.data[key] = value
s = MemoryStorage()s.save_all({"a": "1"})print(s.data) # {'a': '1'}抽象方法没实现完的类不能实例化
from abc import ABC, abstractmethod
class Storage(ABC): @abstractmethod def save(self, key: str) -> None: ...
class Broken(Storage): pass
try: Broken()except TypeError as e: print("TypeError:", e)抽象属性:@property 叠 @abstractmethod
from abc import ABC, abstractmethod
class Plugin(ABC): @property @abstractmethod def name(self) -> str: ...
class Hello(Plugin): @property def name(self) -> str: return "hello"
print(Hello().name) # hello鸭子类型:不声明接口,直接调用方法
class Duck: def speak(self) -> str: return "嘎"
class Robot: def speak(self) -> str: return "滴"
for thing in [Duck(), Robot()]: print(thing.speak())泛型函数:类型参数写在函数名后的方括号里(3.12+),详见第 9 章
def first[T](items: list[T]) -> T: return items[0]
print(first([3, 1, 2]), first(["a", "b"])) # 3 a泛型类
class Stack[T]: def __init__(self) -> None: self._items: list[T] = []
def push(self, item: T) -> None: self._items.append(item)
def pop(self) -> T: return self._items.pop()
s = Stack[int]()s.push(1)s.push(2)print(s.pop()) # 2多个类型参数
def invert[K, V](d: dict[K, V]) -> dict[V, K]: return {v: k for k, v in d.items()}
print(invert({"a": 1, "b": 2})) # {1: 'a', 2: 'b'}上界:类型参数必须是某个类型的子类型
from collections.abc import Sized
def longer[T: Sized](a: T, b: T) -> T: return a if len(a) >= len(b) else b
print(longer("abc", "de"), longer([1], [1, 2])) # abc [1, 2]约束:类型参数只能是列出的几种类型之一
def concat[S: (str, bytes)](a: S, b: S) -> S: return a + b
print(concat("ab", "cd"), concat(b"x", b"y")) # abcd b'xy'泛型类型别名:type 语句(3.12+)
type Pair[T] = tuple[T, T]
def swap[T](p: Pair[T]) -> Pair[T]: return p[1], p[0]
print(swap((1, 2))) # (2, 1)类型参数只给类型检查器看,运行时不检查
class Box[T]: def __init__(self, item: T) -> None: self.item = item
b = Box[int]("不是整数") # 类型检查器报错,运行时照样执行print(b.item) # 不是整数抛出异常
def sqrt(x: float) -> float: if x < 0: raise ValueError(f"负数不能开平方:{x}") return x ** 0.5
print(sqrt(9)) # 3.0try / except / else / finally:else 在没出错时执行,finally 总会执行,详见第 10 章
try: n = int("42")except ValueError as e: print("不是数字:", e)else: print("成功:", n) # 成功: 42finally: print("结束") # 结束一次捕获几种异常
try: {}["missing"]except (KeyError, IndexError) as e: print(type(e).__name__, e) # KeyError 'missing'自定义异常:继承 Exception
class ConfigError(Exception): def __init__(self, path: str, reason: str) -> None: super().__init__(f"{path}:{reason}") self.path = path
try: raise ConfigError("app.toml", "端口超出范围")except ConfigError as e: print(e, e.path) # app.toml:端口超出范围 app.tomlraise … from:抛出新异常并保留原因,详见第 10 章
def load_port(text: str) -> int: try: return int(text) except ValueError as e: raise RuntimeError(f"端口配置无效:{text!r}") from e
try: load_port("abc")except RuntimeError as e: print(e, "原因:", repr(e.__cause__))在 except 里写不带参数的 raise,原样重新抛出
try: try: 1 / 0 except ZeroDivisionError: print("记一笔日志") raiseexcept ZeroDivisionError as e: print("外层收到:", e) # 外层收到: division by zerowith 保证资源被清理:离开代码块时自动关闭文件,出错也一样
from pathlib import Path
Path("demo.txt").write_text("hi", encoding="utf-8")with open("demo.txt", encoding="utf-8") as f: print(f.read()) # hiprint(f.closed) # True一个 with 管多个资源,括号里换行写(3.10+)
with ( open("a.txt", "w", encoding="utf-8") as fa, open("b.txt", "w", encoding="utf-8") as fb,): fa.write("A") fb.write("B")用 contextlib.contextmanager 写自己的 with 资源:yield 之前是进入,finally 里是清理
import timefrom contextlib import contextmanager
@contextmanagerdef timer(label: str): start = time.perf_counter() try: yield finally: print(f"{label}用时 {time.perf_counter() - start:.3f} 秒")
with timer("求和"): sum(range(1_000_000))忽略某种异常:contextlib.suppress
from contextlib import suppressfrom pathlib import Path
with suppress(FileNotFoundError): Path("不存在.txt").unlink()print("继续执行")同时处理多个异常:ExceptionGroup 和 except*(3.11+),TaskGroup 出错时抛的就是它
try: raise ExceptionGroup("两个错误", [ValueError("a"), TypeError("b")])except* ValueError as eg: print("值错误:", eg.exceptions)except* TypeError as eg: print("类型错误:", eg.exceptions)空值是 None,判断用 is None,不用 ==
result = Noneprint(result is None, result is not None) # True False可空类型写 X | None(Optional[X] 是旧写法,意思相同),详见第 6 章
def find_user(uid: int) -> str | None: return "小明" if uid == 1 else None
print(find_user(1), find_user(2)) # 小明 None没有 ?.:先判断 is not None 再访问
def find_user(uid: int) -> str | None: return None
name = find_user(2)length = len(name) if name is not None else 0print(length) # 0字典的键可能不存在:用 get
config = {"host": "localhost"}print(config.get("port"), config.get("port", 8080)) # None 8080属性可能不存在:用 getattr 带默认值
class Cfg: host = "localhost"
print(getattr(Cfg, "port", None)) # Nonex or 默认值 会把 0、空串、空列表也当成空,换成默认值
count = 0print(count or 10) # 10,0 被换掉了只在真正是 None 时才用默认值
count = 0print(count if count is not None else 10) # 0函数没写 return 或 return 后面没有值,返回 None
def log(msg: str) -> None: print(msg)
print(log("hi")) # 先打印 hi,再打印 None导入标准库模块
import math
print(math.sqrt(16), math.pi) # 4.0 3.141592653589793只导入需要的名字
from pathlib import Pathfrom collections import Counter, deque
print(Path("a") / "b", Counter("aab")["a"], deque([1, 2]))导入时起别名
import datetime as dt
print(dt.date(2026, 10, 7).isoformat()) # 2026-10-07导入自己的文件:同目录的 utils.py 就是模块 utils
def add(a: int, b: int) -> int: return a + b
# main.pyfrom utils import addprint(add(1, 2)) # 3包:带 __init__.py 的目录;包内用 . 开头的相对导入,详见第 11 章
# shop/prices.pyPRICES = {"苹果": 3}
# shop/cart.pyfrom .prices import PRICES
def total(items: list[str]) -> int: return sum(PRICES[i] for i in items)
# main.pyfrom shop.cart import totalprint(total(["苹果", "苹果"])) # 6if __name__ == "__main__"::文件被直接运行时才执行,被导入时不执行
def main() -> None: print("直接运行")
if __name__ == "__main__": main()__all__ 规定 from 模块 import * 导出哪些名字
__all__ = ["area"]
def area(r: float) -> float: return 3.14 * r * r
def helper() -> None: pass
# main.pyfrom geometry import *print(area(1), "helper" in dir()) # 3.14 False下划线开头表示内部使用:只是约定,import * 不导入,直接写名字仍能访问
_secret = "s3cr3t"public = "ok"
# main.pyimport configprint(config.public, config._secret) # ok s3cr3t查看模块从哪里加载、有哪些名字
import json
print(json.__file__)print([n for n in dir(json) if not n.startswith("_")][:4])迭代与生成器
Section titled “迭代与生成器”iter 取迭代器,next 取下一个,取完抛 StopIteration,详见第 13 章
it = iter([1, 2])print(next(it), next(it), next(it, "完")) # 1 2 完自定义迭代器:实现 __iter__ 和 __next__
class Countdown: def __init__(self, start: int) -> None: self.current = start
def __iter__(self): return self
def __next__(self) -> int: if self.current <= 0: raise StopIteration self.current -= 1 return self.current + 1
print(list(Countdown(3))) # [3, 2, 1]生成器函数:用 yield,调用后得到一个惰性的迭代器
def fib(): a, b = 0, 1 while True: yield a a, b = b, a + b
gen = fib()print([next(gen) for _ in range(8)]) # [0, 1, 1, 2, 3, 5, 8, 13]自定义可迭代类最简单的写法:__iter__ 写成生成器
class Team: def __init__(self, *members: str) -> None: self.members = members
def __iter__(self): for m in self.members: yield m.upper()
print(list(Team("ann", "bob"))) # ['ANN', 'BOB']生成器表达式:推导式换成圆括号,不建中间列表
print(sum(x * x for x in range(10))) # 285yield from:把另一个可迭代对象的元素逐个交出去
def flatten(nested): for item in nested: if isinstance(item, list): yield from flatten(item) else: yield item
print(list(flatten([1, [2, [3, 4]], 5]))) # [1, 2, 3, 4, 5]itertools.islice:从(可能无限的)迭代器里取一段
from itertools import count, islice
print(list(islice(count(10), 3))) # [10, 11, 12]itertools.chain:把几个可迭代对象首尾相接
from itertools import chain
print(list(chain([1, 2], (3,), "ab"))) # [1, 2, 3, 'a', 'b']itertools.batched:按固定大小分批(3.12+)
from itertools import batched
print(list(batched("abcdefg", 3))) # [('a', 'b', 'c'), ('d', 'e', 'f'), ('g',)]生成器只能遍历一次
squares = (x * x for x in range(3))print(list(squares), list(squares)) # [0, 1, 4] []启动线程并等它结束,详见第 17 章
import threading
def work(n: int) -> None: print(f"线程 {n} 完成")
threads = [threading.Thread(target=work, args=(i,)) for i in range(3)]for t in threads: t.start()for t in threads: t.join()锁:with lock 保护共享数据
import threading
counter = 0lock = threading.Lock()
def add() -> None: global counter for _ in range(100_000): with lock: counter += 1
threads = [threading.Thread(target=add) for _ in range(4)]for t in threads: t.start()for t in threads: t.join()print(counter) # 400000线程池:ThreadPoolExecutor.map 并发执行并按顺序取结果(适合 I/O 密集)
from concurrent.futures import ThreadPoolExecutor
def fetch(n: int) -> int: return n * n
with ThreadPoolExecutor(max_workers=4) as pool: print(list(pool.map(fetch, range(5)))) # [0, 1, 4, 9, 16]线程之间传数据:queue.Queue,详见第 20 章
import queueimport threading
q: queue.Queue[int | None] = queue.Queue()
def consumer() -> None: while (item := q.get()) is not None: print("收到", item)
t = threading.Thread(target=consumer)t.start()for i in range(3): q.put(i)q.put(None) # 约定 None 表示结束t.join()进程池:CPU 密集的任务用多进程,入口必须放在 if __name__ == "__main__": 里,详见第 18 章
from multiprocessing import Pool
def square(n: int) -> int: return n * n
if __name__ == "__main__": with Pool(4) as pool: print(pool.map(square, range(5))) # [0, 1, 4, 9, 16]协程:async def 定义,await 等待,asyncio.run 启动,详见第 19 章
import asyncio
async def hello() -> str: await asyncio.sleep(0.1) return "你好"
async def main() -> None: print(await hello()) # 你好
asyncio.run(main())asyncio.gather:同时等待多个协程,结果按传入顺序
import asyncio
async def fetch(n: int) -> int: await asyncio.sleep(0.1) return n * 10
async def main() -> None: print(await asyncio.gather(fetch(1), fetch(2), fetch(3))) # [10, 20, 30]
asyncio.run(main())asyncio.TaskGroup:结构化并发,一个任务出错其余自动取消(3.11+),详见第 21 章
import asyncio
async def fetch(n: int) -> int: await asyncio.sleep(0.1) return n * 10
async def main() -> None: async with asyncio.TaskGroup() as tg: t1 = tg.create_task(fetch(1)) t2 = tg.create_task(fetch(2)) print(t1.result(), t2.result()) # 10 20
asyncio.run(main())超时:asyncio.timeout(3.11+),超时抛 TimeoutError
import asyncio
async def main() -> None: try: async with asyncio.timeout(0.1): await asyncio.sleep(1) except TimeoutError: print("超时了")
asyncio.run(main())在协程里调用阻塞函数:asyncio.to_thread,不卡住事件循环
import asyncioimport time
def blocking_io() -> str: time.sleep(0.1) return "读完了"
async def main() -> None: print(await asyncio.to_thread(blocking_io)) # 读完了
asyncio.run(main())文件与 JSON
Section titled “文件与 JSON”整个读写文本文件:Path.write_text 和 Path.read_text,一律写 encoding="utf-8"
from pathlib import Path
p = Path("note.txt")p.write_text("第一行\n第二行\n", encoding="utf-8")print(p.read_text(encoding="utf-8").splitlines()) # ['第一行', '第二行']逐行读大文件:open 也要写 encoding="utf-8"(Windows 上 3.14 及之前的版本不写会按系统编码读写,中文系统是 GBK)
from pathlib import Path
Path("log.txt").write_text("a\nb\n", encoding="utf-8")with open("log.txt", encoding="utf-8") as f: for line in f: print(line.rstrip("\n"))追加写入:模式 "a"
with open("log.txt", "a", encoding="utf-8") as f: f.write("新的一行\n")路径拼接用 /,拆出文件名、扩展名、父目录
from pathlib import Path
p = Path("data") / "2026" / "report.csv"print(p.name, p.stem, p.suffix, p.parent.as_posix()) # report.csv report .csv data/2026判断存在、建多级目录、删文件
from pathlib import Path
d = Path("out/logs")d.mkdir(parents=True, exist_ok=True)f = d / "x.txt"f.write_text("x", encoding="utf-8")print(d.exists(), f.is_file()) # True Truef.unlink()列出目录下的直接子项
from pathlib import Path
for child in sorted(Path(".").iterdir()): print(child.name, "目录" if child.is_dir() else "文件")按模式查找文件:glob 当前目录,rglob 递归所有子目录
from pathlib import Path
print(sorted(p.name for p in Path(".").glob("*.py")))print(sorted(p.as_posix() for p in Path(".").rglob("*.txt")))对象转 JSON 字符串:中文原样输出、缩进两格
import json
data = {"name": "小明", "tags": ["a", "b"], "ok": True}print(json.dumps(data, ensure_ascii=False, indent=2))JSON 字符串转对象
import json
obj = json.loads('{"n": 1, "items": [1, 2], "x": null}')print(obj, obj["items"][1]) # {'n': 1, 'items': [1, 2], 'x': None} 2读写 JSON 文件
import jsonfrom pathlib import Path
p = Path("config.json")p.write_text(json.dumps({"port": 8080}, ensure_ascii=False, indent=2), encoding="utf-8")config = json.loads(p.read_text(encoding="utf-8"))print(config["port"]) # 8080临时目录:离开 with 自动删除
import tempfilefrom pathlib import Path
with tempfile.TemporaryDirectory() as tmp: p = Path(tmp) / "t.txt" p.write_text("临时", encoding="utf-8") print(p.exists()) # Trueprint(p.exists()) # False标准库 unittest 的最小测试文件:测试类继承 TestCase,方法名以 test 开头
def add(a: int, b: int) -> int: return a + b
# test_calc.pyimport unittestfrom calc import add
class TestAdd(unittest.TestCase): def test_add(self) -> None: self.assertEqual(add(1, 2), 3)
if __name__ == "__main__": unittest.main()运行当前目录下所有 test*.py
python -m unittest只跑一个文件,显示每个测试的名字
python -m unittest -v test_calc断言会抛出某种异常
import unittest
class TestErrors(unittest.TestCase): def test_bad_int(self) -> None: with self.assertRaises(ValueError): int("abc")
if __name__ == "__main__": unittest.main()每个测试前准备数据:setUp
import unittest
class TestStack(unittest.TestCase): def setUp(self) -> None: self.items = [1, 2, 3]
def test_pop(self) -> None: self.assertEqual(self.items.pop(), 3)
def test_len(self) -> None: self.assertEqual(len(self.items), 3)
if __name__ == "__main__": unittest.main()一个测试里跑多组数据:subTest,某组失败不影响其余组
import unittest
class TestSquare(unittest.TestCase): def test_square(self) -> None: for n, expected in [(2, 4), (3, 9), (-1, 1)]: with self.subTest(n=n): self.assertEqual(n * n, expected)
if __name__ == "__main__": unittest.main()pytest(第三方):测试写成普通函数加 assert,用 uv 装好后运行
uv add --dev pytestuv run pytest格式化与 lint
Section titled “格式化与 lint”ruff(第三方,事实标准)加为开发依赖
uv add --dev ruff格式化代码
uv run ruff format .检查代码问题
uv run ruff check .自动修复能修的问题
uv run ruff check --fix .不装进项目、临时跑一次
uvx ruff check .ruff 的配置写在 pyproject.toml 里
[tool.ruff]line-length = 100
[tool.ruff.lint]select = ["E", "F", "I", "UP"]只检查语法、不运行(标准库)
python -m py_compile hello.py可变默认参数:def f(items=[]) → 默认列表在定义时只建一次,每次调用共用 → 默认值写 None,在函数里新建
def bad(item, items=[]): items.append(item) return items
print(bad(1), bad(2)) # [1, 2] [1, 2]is 和 ==:用 is 比较内容 → 两个内容相同的对象 is 为假 → 比较值用 ==,is 只用于 None,详见第 5 章
a, b = [1, 2], [1, 2]print(a == b, a is b) # True False整数缓存:小整数(CPython 是 -5 到 256)is 为真,大整数可能为假 → 结果随实现和写法变 → 数字永远用 ==
print(int("256") is int("256"), int("257") is int("257")) # True False循环里的闭包晚绑定:在循环里建 lambda → 调用时才读变量,全都拿到最后一个值 → 用默认参数在定义时绑定
funcs = [lambda: i for i in range(3)]print([f() for f in funcs]) # [2, 2, 2]funcs = [lambda i=i: i for i in range(3)]print([f() for f in funcs]) # [0, 1, 2]import 循环:两个模块在顶层互相 from … import → 先导入的那个还没执行完,报 ImportError → 把共用部分挪到第三个模块,或把 import 挪进函数里
import b
def name() -> str: return "a"
# b.pydef greet() -> str: from a import name # 挪进函数里:调用时 a 已经加载完 return f"来自 {name()}"
# main.pyimport aimport bprint(b.greet()) # 来自 a浅拷贝:copy() 或切片复制嵌套列表 → 内层列表仍是同一个,改一处两边都变 → 嵌套结构用 copy.deepcopy
import copy
grid = [[0, 0], [0, 0]]shallow, deep = grid.copy(), copy.deepcopy(grid)grid[0][0] = 9print(shallow[0][0], deep[0][0]) # 9 0[[0] * 2] * 2 建二维列表 → 几行是同一个列表对象 → 用推导式
bad = [[0] * 2] * 2bad[0][0] = 1good = [[0] * 2 for _ in range(2)]good[0][0] = 1print(bad, good) # [[1, 0], [1, 0]] [[1, 0], [0, 0]]一边遍历一边删除列表元素 → 下标错位,会漏掉元素 → 用推导式生成新列表
nums = [1, 2, 2, 3]for n in nums: if n == 2: nums.remove(n)print(nums) # [1, 2, 3],漏删一个print([n for n in [1, 2, 2, 3] if n != 2]) # [1, 3]print 缓冲:输出重定向到文件或管道时 → 不是每行立刻写出,进度看不到 → 加 flush=True
import time
for i in range(3): print(f"进度 {i}", flush=True) time.sleep(0.1)Windows 下的文件编码:open 不写 encoding → 3.14 及之前的版本按系统编码(中文系统是 GBK)读 UTF-8 文件,报 UnicodeDecodeError 或乱码 → 每次都写 encoding="utf-8",详见第 14 章
from pathlib import Path
Path("zh.txt").write_text("中文", encoding="utf-8")print(Path("zh.txt").read_text(encoding="utf-8")) # 中文