跳转到内容

附录 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 / Linux
curl -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 b
print(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

新建项目,生成 pyproject.toml、main.py 等文件(uv,第三方,下同)

终端窗口
uv init demo
cd demo

添加依赖,同时更新 pyproject.toml 和 uv.lock

终端窗口
uv add requests

添加只在开发时用的依赖

终端窗口
uv add --dev pytest

移除依赖

终端窗口
uv remove requests

pyproject.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 = 0
count = "零"
print(count) # 零

带类型标注的变量(只给类型检查器看,运行时不检查),详见第 3 章

name: str = "Ada"
print(name) # Ada

多重赋值

x, y = 1, 2
print(x, y) # 1 2

交换两个变量

a, b = 1, 2
a, b = b, a
print(a, b) # 2 1

常量靠全大写命名约定,语言不阻止修改

MAX_RETRIES = 3
print(MAX_RETRIES) # 3

用 Final 标注常量,类型检查器会报告重新赋值,运行时不拦,详见第 4 章

from typing import Final
RATE: Final = 0.05
print(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 * 10
print(i, last) # 2 20

删除一个名字

x = 1
del x
print("x" in globals()) # False

字符串转整数,可指定进制

print(int("42")) # 42
print(int("ff", 16)) # 255

字符串转浮点数,浮点数转整数(向零截断)

print(float("3.14")) # 3.14
print(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 math
from decimal import Decimal
print(0.1 + 0.2) # 0.30000000000000004
print(math.isclose(0.1 + 0.2, 0.3)) # True
print(Decimal("0.1") + Decimal("0.2")) # 0.3

round 遇到 .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 = "小明", 18
print(f"{name} 今年 {age} 岁") # 小明 今年 18 岁

f-string 的格式说明:小数位、千分位、百分比、宽度和对齐

pi, n = 3.14159, 1234567
print(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\table

len 数的是码点,不是字节,详见第 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] = 11
print(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["小刚"] = 19
ages["小明"] = 17
del 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 1

if / elif / else,条件不加括号,代码块靠缩进

score = 85
if score >= 90:
grade = "A"
elif score >= 80:
grade = "B"
else:
grade = "C"
print(grade) # B

三元表达式

n = 7
print("奇数" if n % 2 else "偶数") # 奇数

链式比较

x = 5
print(0 < x < 10) # True

for 遍历任何可迭代对象;数字范围用 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 = 0
while True:
n += 1
if n % 2 == 0:
continue
if n > 5:
break
print(n) # 依次打印 1 3 5

for … else:循环没被 break 打断才执行 else

for n in [3, 5, 7]:
if n % 2 == 0:
print("找到偶数", n)
break
else:
print("没有偶数") # 没有偶数

match 匹配字面量,| 表示或,_ 兜底(3.10+)

code = 404
match 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,0
print(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
@logged
def 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
@dataclass
class 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_checkable
class Closable(Protocol):
def close(self) -> None: ...
class Conn:
def close(self) -> None:
print("关闭")
print(isinstance(Conn(), Closable), isinstance(42, Closable)) # True False

ABC 加 @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.0

try / except / else / finally:else 在没出错时执行,finally 总会执行,详见第 10 章

try:
n = int("42")
except ValueError as e:
print("不是数字:", e)
else:
print("成功:", n) # 成功: 42
finally:
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.toml

raise … 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("记一笔日志")
raise
except ZeroDivisionError as e:
print("外层收到:", e) # 外层收到: division by zero

with 保证资源被清理:离开代码块时自动关闭文件,出错也一样

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()) # hi
print(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 time
from contextlib import contextmanager
@contextmanager
def 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 suppress
from 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 = None
print(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 0
print(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)) # None

x or 默认值 会把 0、空串、空列表也当成空,换成默认值

count = 0
print(count or 10) # 10,0 被换掉了

只在真正是 None 时才用默认值

count = 0
print(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 Path
from 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

utils.py
def add(a: int, b: int) -> int:
return a + b
# main.py
from utils import add
print(add(1, 2)) # 3

包:带 __init__.py 的目录;包内用 . 开头的相对导入,详见第 11 章

shop/__init__.py
# shop/prices.py
PRICES = {"苹果": 3}
# shop/cart.py
from .prices import PRICES
def total(items: list[str]) -> int:
return sum(PRICES[i] for i in items)
# main.py
from shop.cart import total
print(total(["苹果", "苹果"])) # 6

if __name__ == "__main__"::文件被直接运行时才执行,被导入时不执行

def main() -> None:
print("直接运行")
if __name__ == "__main__":
main()

__all__ 规定 from 模块 import * 导出哪些名字

geometry.py
__all__ = ["area"]
def area(r: float) -> float:
return 3.14 * r * r
def helper() -> None:
pass
# main.py
from geometry import *
print(area(1), "helper" in dir()) # 3.14 False

下划线开头表示内部使用:只是约定,import * 不导入,直接写名字仍能访问

config.py
_secret = "s3cr3t"
public = "ok"
# main.py
import config
print(config.public, config._secret) # ok s3cr3t

查看模块从哪里加载、有哪些名字

import json
print(json.__file__)
print([n for n in dir(json) if not n.startswith("_")][:4])

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))) # 285

yield 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 = 0
lock = 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 queue
import 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 asyncio
import time
def blocking_io() -> str:
time.sleep(0.1)
return "读完了"
async def main() -> None:
print(await asyncio.to_thread(blocking_io)) # 读完了
asyncio.run(main())

整个读写文本文件: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 True
f.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 json
from 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 tempfile
from pathlib import Path
with tempfile.TemporaryDirectory() as tmp:
p = Path(tmp) / "t.txt"
p.write_text("临时", encoding="utf-8")
print(p.exists()) # True
print(p.exists()) # False

标准库 unittest 的最小测试文件:测试类继承 TestCase,方法名以 test 开头

calc.py
def add(a: int, b: int) -> int:
return a + b
# test_calc.py
import unittest
from 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 pytest
uv run pytest

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 挪进函数里

a.py
import b
def name() -> str:
return "a"
# b.py
def greet() -> str:
from a import name # 挪进函数里:调用时 a 已经加载完
return f"来自 {name()}"
# main.py
import a
import b
print(b.greet()) # 来自 a

浅拷贝:copy() 或切片复制嵌套列表 → 内层列表仍是同一个,改一处两边都变 → 嵌套结构用 copy.deepcopy

import copy
grid = [[0, 0], [0, 0]]
shallow, deep = grid.copy(), copy.deepcopy(grid)
grid[0][0] = 9
print(shallow[0][0], deep[0][0]) # 9 0

[[0] * 2] * 2 建二维列表 → 几行是同一个列表对象 → 用推导式

bad = [[0] * 2] * 2
bad[0][0] = 1
good = [[0] * 2 for _ in range(2)]
good[0][0] = 1
print(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")) # 中文