跳至内容
Python 3 基础速查

Python 3 基础速查

面向复习与日常开发的 Python 3 基础速查,涵盖核心语法、对象与类型、容器、函数、异常、模块和工程常识。

  • Python 3
  • 基础语法
  • 工程实践
  • Python
  • 字符串
  • 集合

这是一份面向面试复习和日常开发的 Python 3 基础速查,重点整理名称绑定、可变对象、作用域、异常处理和环境隔离等高频易错点, 可按目录直接跳读。

版本:示例最低需要 Python 3.13,并按 Python 3.15 文档复核;实际项目以自身声明的 Python 版本为准。

1. Python、CPython 与交互环境

名称定位
Python语言及其规范
CPython最常用、由 Python 项目维护的实现
PyPy包含 JIT 的另一种实现,第三方扩展兼容性需要单独核对
IPython、Jupyter交互环境,通常运行在 CPython 之上

将 Python 只概括为“解释型语言”并不准确。以默认 CPython 为例,源码会先编译为字节码,再由虚拟机执行,并可能缓存 .pyc; 具体执行方式取决于实现。Python 通常以一定运行时开销换取表达力、生态和开发效率,是否满足性能要求应以实测为准。

2. 安装、运行与环境隔离

尚未安装时,从 python.org 下载页 获取当前稳定版。新项目可以选择当前稳定版;加入已有项目时, 以项目声明的 Python 版本和依赖兼容范围为准。安装完成后先确认终端实际调用的是哪个版本。

2.1 确认解释器

macOS 或 Linux 通常使用 python3,Windows 可使用 py 启动器:

代码块 Shell
1
2
3
4
5
6
7
# macOS / Linux
python3 --version
# 输出格式:Python 3.13.11

# Windows PowerShell
py --version
# 输出格式:Python 3.13.11

直接运行解释器会进入 REPL,适合验证表达式;把代码保存为 hello.py 后则可以运行脚本:

代码块 Python
1
2
3
4
name = "Python"
print(f"Hello, {name}!")

# 输出:Hello, Python!
代码块 Shell
1
2
3
4
5
6
7
# macOS / Linux
python3 hello.py
# 输出:Hello, Python!

# Windows PowerShell
py hello.py
# 输出:Hello, Python!

2.2 每个项目使用独立虚拟环境

虚拟环境隔离解释器入口和第三方包,避免不同项目互相覆盖依赖。.venv 是可丢弃的本地产物,不应提交到 Git。

代码块 Shell
1
2
3
4
5
6
7
8
9
# macOS / Linux
python3 -m venv .venv
source .venv/bin/activate
# 成功时通常无输出;当前 shell 的 Python 已切换到 .venv。

# Windows PowerShell
py -m venv .venv
.venv\Scripts\Activate.ps1
# 成功时通常无输出;当前 PowerShell 的 Python 已切换到 .venv。

激活后统一使用当前环境中的 python。用 python -m pip 比直接使用 pip 更明确,因为它能保证安装目标就是当前解释器:

代码块 Shell
1
2
3
4
5
6
python --version
# 输出格式:Python 3.13.x
python -m pip --version
# 输出格式:pip <版本> from <.venv 路径>/site-packages/pip (python 3.13)
python -m pip install package_name
# 输出:安装日志取决于包、索引和缓存;package_name 需要替换成真实包名。

不要用 sudo pip install ... 修改系统 Python。项目依赖还应记录在 pyproject.toml 或锁文件中,虚拟环境本身则随时重建。

3. 语法、注释与命名

3.1 缩进是语法的一部分

Python 用缩进表示代码块。惯例是每层 4 个空格,不要混用 Tab 与空格;Tab 的显示宽度也不保证是 4 个空格。

代码块 Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
temperature = 28

if temperature >= 30:
    print("炎热")
elif temperature >= 20:
    print("舒适")
else:
    print("偏冷")

# 输出:舒适

3.2 注释不等于文档字符串

# 开始的是单行注释。三引号创建的是字符串;当它出现在模块、类或函数开头时,才会成为可由工具读取的文档字符串。 因此,不应把三引号字符串笼统称为“多行注释”。

代码块 Python
1
2
3
4
5
6
7
8
def area(width: float, height: float) -> float:
    """返回矩形面积。"""
    return width * height  # 单位由调用方约定


print(area(3.0, 4.0))

# 输出:12.0

注释应该解释“为什么”,而不是逐字复述代码。过时注释比没有注释更危险。

3.3 命名规则与惯例

标识符区分大小写,可以包含 Unicode 字母、数字和下划线,但不能以数字开头,也不能使用关键字。关键字应动态查看:

代码块 Python
1
2
3
4
import keyword

print(keyword.kwlist)
print(keyword.softkwlist)

Python 3.13 输出:

代码块 Text
1
2
# ['False', 'None', 'True', 'and', 'as', 'assert', 'async', 'await', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield']
# ['_', 'case', 'match', 'type']

常用的 PEP 8 命名约定:

  • 变量、函数、模块:snake_case
  • 类:CapWords
  • 常量:UPPER_CASE
  • 内部实现细节:前导下划线,如 _cache

全大写只是“不要重新赋值”的协作约定,不会让对象真正不可变。

4. 名称、对象与类型

4.1 赋值是名称绑定

Python 变量更准确的说法是“名称绑定到对象”,而不是一个固定类型的内存盒子:

代码块 Python
1
2
3
4
5
6
7
8
9
value = 42
print(type(value))

value = "forty-two"
print(type(value))

# 输出:
# <class 'int'>
# <class 'str'>

Python 是动态类型语言:名称可以重新绑定到不同类型的对象;同时它也是强类型语言,通常不会偷偷把无关类型混合运算:

代码块 Python
1
2
3
4
5
6
7
# TypeError: unsupported operand type(s) for +: 'int' and 'str'
# total = 1 + "2"

total = 1 + int("2")
print(total)

# 输出:3

多个名称可以指向同一个可变对象:

代码块 Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
first = [1, 2]
second = first
second.append(3)

print(first)
print(first is second)

# 输出:
# [1, 2, 3]
# True

需要独立的浅拷贝可使用 first.copy() 或 first[:];嵌套对象是否也要复制,需要根据数据结构决定是否使用 copy.deepcopy()。

4.2 常见内置类型

类别类型是否可变示例
空值NoneType否None
布尔bool否True、False
数值int、float、complex否42、3.14、1+2j
文本与字节str、bytes否"你好"、b"data"
序列list、tuple、range仅 list 可变[1, 2]、(1, 2)
映射dict是{"name": "Ada"}
集合set、frozenset仅 set 可变{1, 2}

“对象不可变”表示对象自身的值不能原地改变,不代表指向它的名称不能重新绑定。

4.3 == 与 is

== 比较值,is 比较是否为同一个对象。判断空值时使用 is None;不要依赖小整数或短字符串的缓存细节。

代码块 Python
1
2
3
4
5
6
result = None

if result is None:
    print("暂无结果")

# 输出:暂无结果

4.4 真值规则

None、False、数值零以及空字符串和空容器通常为假,其余对象通常为真:

代码块 Python
1
2
3
4
5
6
items: list[str] = []

if not items:
    print("列表为空")

# 输出:列表为空

if value: 同时把 0、空值和空容器视为假。如果业务上必须区分这些情况,就应写出精确条件。

5. 数值与运算符

常用算术运算符包括 +、-、*、/、//、% 和 **。/ 总是返回浮点数,// 是向负无穷取整的整除:

代码块 Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
print(7 / 2)
print(7 // 2)
print(-7 // 2)
print(2**10)

# 输出:
# 3.5
# 3
# -4
# 1024

int() 把浮点数向零截断,不等于数学上的向下取整:

代码块 Python
1
2
3
4
5
6
7
8
import math

print(int(-1.8))
print(math.floor(-1.8))

# 输出:
# -1,int() 向零截断
# -2,math.floor() 向下取整

浮点数采用二进制表示,很多十进制小数不能精确存储。比较计算结果可使用 math.isclose();金额等十进制定点业务可使用 decimal.Decimal,并从字符串构造:

代码块 Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import math
from decimal import Decimal

print(math.isclose(0.1 + 0.2, 0.3))
price = Decimal("19.90")
print(price)

# 输出:
# True
# 19.90

比较运算可以链式书写,逻辑运算使用 and、or、not:

代码块 Python
1
2
3
4
5
age = 24
if 18 <= age < 60:
    print("年龄在目标区间")

# 输出:年龄在目标区间

6. 字符串、字节与格式化输出

6.1 字符串是不可变的 Unicode 文本

代码块 Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
language = "Python"

print(language[0])
print(language[-1])
print(language[1:4])
print(language.lower())

# 输出:
# P
# n
# yth
# python

切片遵循左闭右开区间 [start:stop:step]。字符串方法返回新对象,不会原地修改原字符串。

6.2 优先使用 f-string

% 格式化和 str.format() 仍然有效,但新代码通常优先使用可读性更好的 f-string:

代码块 Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
name = "Ada"
score = 95.678
student_id = 7

print(f"{name} 的成绩是 {score:.2f}")
print(f"学号:{student_id:03d}")
print(f"调试值:{score=}")

# 输出:
# Ada 的成绩是 95.68
# 学号:007
# 调试值:score=95.678

转义字符 \n 表示换行,\t 表示水平制表符;制表符显示成多宽由终端或编辑器决定。

代码块 Python
1
2
print("第一行\n第二行")
print("A", "B", "C", sep=" | ", end="\n")

输出:

代码块 Text
1
2
3
# 第一行
# 第二行
# A | B | C

6.3 文本与字节要显式转换

网络、压缩和二进制文件通常处理 bytes;界面和业务文本通常处理 str。二者不能直接拼接:

代码块 Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
text = "你好,Python"
payload = text.encode("utf-8")
restored = payload.decode("utf-8")

print(payload)
print(restored)

# 输出:
# b'\xe4\xbd\xa0\xe5\xa5\xbd\xef\xbc\x8cPython'
# 你好,Python

编码是文本到字节,解码是字节到文本。读写文本文件时应明确指定编码。

7. 输入与安全转换

input() 总是返回字符串。转换可能失败,应处理具体的 ValueError:

代码块 Python
1
2
3
4
5
6
7
8
raw_age = input("请输入年龄:").strip()

try:
    age = int(raw_age)
except ValueError:
    print("年龄必须是整数")
else:
    print(f"明年 {age + 1} 岁")

两种输入的执行结果:

代码块 Text
1
2
3
4
5
# 请输入年龄:20
# 明年 21 岁
#
# 请输入年龄:abc
# 年龄必须是整数

密码输入应使用 getpass.getpass(),不要回显后再打印:

代码块 Python
1
2
3
4
5
from getpass import getpass

password = getpass("请输入密码:")

# 输出:仅显示提示文字,输入内容不回显

不要把 eval() 当成类型转换工具。它会执行 Python 表达式,对不可信输入使用可能导致任意代码执行。明确知道目标类型时,使用 int()、float()、str();结构化数据优先使用 JSON:

代码块 Python
1
2
3
4
5
6
import json

settings = json.loads('{"theme": "dark", "page_size": 20}')
print(settings["theme"])

# 输出:dark

处理只包含 Python 字面量的旧数据时可以评估 ast.literal_eval(),但仍应限制输入大小;跨系统交换数据时优先使用 JSON。

8. 核心容器

8.1 列表与元组

列表适合可变的有序数据,元组适合结构固定的记录或不可变序列:

代码块 Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
tasks = ["read", "code"]
tasks.append("review")
tasks.extend(["test", "release"])

point = (10, 20)
x, y = point

print(tasks)
print(x, y)

# 输出:
# ['read', 'code', 'review', 'test', 'release']
# 10 20

元组本身不能增删或替换元素,但元素仍可能指向列表等可变对象,因此元组不保证“深层不可变”,也不一定可哈希。

避免用乘法创建共享的嵌套可变对象:

代码块 Python
1
2
3
4
5
6
7
8
# 错误:两行引用同一个内部列表
# matrix = [[0] * 3] * 2

matrix = [[0] * 3 for _ in range(2)]
matrix[0][0] = 1
print(matrix)

# 输出:[[1, 0, 0], [0, 0, 0]]

8.2 字典

字典保存键值映射,键必须可哈希。现代 Python 的字典保留插入顺序,但如果业务需要排序,仍应显式使用 sorted()。

代码块 Python
1
2
3
4
5
6
7
8
user = {"name": "Ada", "age": 36}
user["city"] = "London"

for key, value in user.items():
    print(key, value)

email = user.get("email")  # 缺失时返回 None
print(email)

输出:

代码块 Text
1
2
3
4
# name Ada
# age 36
# city London
# None

mapping[key] 在键缺失时抛出 KeyError;mapping.get(key) 适合“缺失是正常情况”的读取。不要用 get() 掩盖本应存在的键。

8.3 集合

集合保存不重复、可哈希的元素,适合去重和成员判断;集合本身没有可依赖的展示顺序。

代码块 Python
1
2
3
4
5
6
7
8
required = {"title", "content"}
provided = {"title", "content", "tags", "author"}

print(required <= provided)
print(provided - required)

# 输出:True
# 示例输出:{'tags', 'author'},元素顺序不保证

8.4 推导式

推导式适合简短、单一的映射或过滤;逻辑复杂时使用普通循环更清楚。

代码块 Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
numbers = range(10)
squares = [number**2 for number in numbers if number % 2 == 0]
index = {name: position for position, name in enumerate(["Ada", "Guido"])}

print(squares)
print(index)

# 输出:
# [0, 4, 16, 36, 64]
# {'Ada': 0, 'Guido': 1}

生成器表达式使用圆括号,按需产生值,不会一次构造完整列表:

代码块 Python
1
2
3
4
total = sum(number**2 for number in range(1_000_000))
print(total)

# 输出:333332833333500000

9. 分支、循环与模式匹配

9.1 if、for 与 while

Python 的 for 遍历可迭代对象,而不是只提供计数循环:

代码块 Python
1
2
3
4
names = ["Ada", "Guido", "Grace"]

for position, name in enumerate(names, start=1):
    print(position, name)

输出:

代码块 Text
1
2
3
# 1 Ada
# 2 Guido
# 3 Grace

并行遍历多个序列可使用 zip():

代码块 Python
1
2
3
4
5
names = ["Ada", "Guido"]
scores = [98, 95]

for name, score in zip(names, scores, strict=True):
    print(f"{name}: {score}")

输出:

代码块 Text
1
2
# Ada: 98
# Guido: 95

strict=True 会在序列长度不一致时抛出 ValueError,适合长度必须相同的业务数据。

while 适合结束条件不是“遍历完某个集合”的情况。循环必须保证状态朝退出条件推进:

代码块 Python
1
2
3
4
remaining = 3
while remaining > 0:
    print(remaining)
    remaining -= 1

输出:

代码块 Text
1
2
3
# 3
# 2
# 1

9.2 match 是结构化模式匹配

Python 3.10+ 的 match 不只是其他语言中 switch 的别名,它还能解构数据:

代码块 Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
def describe(command: tuple[str, ...]) -> str:
    match command:
        case ("move", direction, distance):
            return f"向 {direction} 移动 {distance}"
        case ("quit",):
            return "退出"
        case _:
            return "未知命令"


print(describe(("move", "东", "3")))
print(describe(("quit",)))
print(describe(("status",)))

# 输出:
# 向 东 移动 3
# 退出
# 未知命令

简单的两三个条件通常继续使用 if 更直观;不要为了使用新语法而使用 match。

10. 函数与作用域

10.1 参数与返回值

函数可以组合位置参数、默认参数和仅限关键字参数:

代码块 Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
from decimal import Decimal


def format_price(amount: Decimal, currency: str = "CNY", *, precision: int = 2) -> str:
    return f"{currency} {amount:.{precision}f}"


print(format_price(Decimal("19.90")))
print(format_price(Decimal("19.90"), precision=1))

# 输出:
# CNY 19.90
# CNY 19.9

* 后的参数必须按关键字传递,可避免布尔值、数字等位置参数含义不清。*args 收集额外位置参数,**kwargs 收集额外关键字参数, 但普通业务函数不应无理由接受任意参数。

函数没有显式 return 时返回 None。返回多个值实际上是返回一个元组:

代码块 Python
1
2
3
4
5
6
7
8
def bounds(values: list[int]) -> tuple[int, int]:
    return min(values), max(values)


lowest, highest = bounds([3, 1, 8])
print(lowest, highest)

# 输出:1 8

10.2 不要使用可变默认值

默认参数在函数定义时求值,只创建一次:

代码块 Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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("read"))
print(add_item("code"))

# 输出:
# ['read']
# ['code']

这个规则同样适用于字典、集合以及自定义的可变对象。

10.3 作用域与闭包

名称查找通常遵循 LEGB:局部(Local)、外层函数(Enclosing)、全局(Global)、内置(Built-in)。尽量通过参数和返回值传递数据, 少依赖可变全局状态。

代码块 Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
def make_multiplier(factor: int):
    def multiply(value: int) -> int:
        return value * factor

    return multiply


double = make_multiplier(2)
print(double(5))

# 输出:10

lambda 适合短小的单表达式函数,例如排序键;一旦需要分支、异常处理或文档,就应使用 def。

代码块 Python
1
2
3
4
5
records = [{"name": "Ada", "score": 98}, {"name": "Guido", "score": 95}]
records.sort(key=lambda record: record["score"], reverse=True)
print(records)

# 输出:[{'name': 'Ada', 'score': 98}, {'name': 'Guido', 'score': 95}]

11. 异常与资源管理

异常用于报告当前层不能正常完成的操作。捕获异常时应尽可能具体,不要用裸 except: 隐藏退出、取消和程序错误。

代码块 Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
def parse_port(raw: str) -> int:
    try:
        port = int(raw)
    except ValueError as exc:
        raise ValueError("端口必须是整数") from exc

    if not 1 <= port <= 65535:
        raise ValueError("端口必须在 1 到 65535 之间")
    return port


print(parse_port("8000"))

# 输出:8000
# parse_port("abc")
# ValueError: 端口必须是整数
# parse_port("70000")
# ValueError: 端口必须在 1 到 65535 之间

异常结构的职责:

  • try:只包围可能失败的最小范围。
  • except:处理明确知道如何恢复或转换的异常。
  • else:仅在没有异常时执行。
  • finally:无论是否异常都执行,适合必须进行的清理。

文件和锁等资源优先使用 with,由上下文管理器保证释放,通常无需手写 finally。假设 notes.txt 包含“第一行”和“第二行”:

代码块 Python
1
2
3
4
5
6
from pathlib import Path

path = Path("notes.txt")
with path.open("r", encoding="utf-8") as file:
    for line in file:
        print(line.rstrip())

输出:

代码块 Text
1
2
# 第一行
# 第二行

数据库连接需要核对具体的上下文协议。例如,sqlite3.Connection 的 with 只管理事务提交或回滚,不会关闭连接,仍需显式调用 close() 或另外使用能负责关闭的上下文管理器。

不要通过“先读取失败,再新建文件”的宽泛异常逻辑判断文件是否存在。真正操作时应优先使用明确的打开模式并捕获具体异常; Path.exists() 适合只需展示状态的场景,不能替代操作本身的错误处理。“先检查再操作”可能有并发竞态,真正的写入约束应由打开模式 或底层存储保证。

12. 文件、路径与结构化数据

12.1 使用 pathlib 表达路径

pathlib.Path 比手工拼接 / 或 \\ 更跨平台:

代码块 Python
1
2
3
4
5
6
7
8
9
from pathlib import Path

data_dir = Path("data")
data_dir.mkdir(parents=True, exist_ok=True)

report_path = data_dir / "report.txt"
report_path.write_text("第一行\n第二行\n", encoding="utf-8")
content = report_path.read_text(encoding="utf-8")
print(content, end="")

输出如下,同时会生成 data/report.txt:

代码块 Text
1
2
# 第一行
# 第二行

read_text() 适合体积可控的文本;大文件应逐行读取,避免一次占用过多内存。

12.2 JSON 只支持有限的数据类型

代码块 Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import json
from pathlib import Path

profile = {"name": "Ada", "skills": ["math", "programming"]}
path = Path("profile.json")

path.write_text(json.dumps(profile, ensure_ascii=False, indent=2), encoding="utf-8")
loaded = json.loads(path.read_text(encoding="utf-8"))
print(loaded)

# 输出:{'name': 'Ada', 'skills': ['math', 'programming']}

JSON 不会自动保存 datetime、Decimal、集合或自定义类,需要先转换为约定的数据结构。不要对不可信数据使用 pickle;反序列化 恶意 pickle 可以执行代码。

13. 模块、包与程序入口

每个 .py 文件都是模块。目录作为常规包时通常包含 __init__.py。导入模块会执行其顶层代码,因此模块顶层应以定义、常量和必要的 轻量初始化为主。

下面只是项目结构示意;可安装包还需要在 pyproject.toml 中声明构建和包发现配置:

代码块 Text
1
2
3
4
5
6
7
8
price_tool/
├── pyproject.toml
├── src/
│   └── price_tool/
│       ├── __init__.py
│       ├── formatter.py
│       └── __main__.py
└── tests/

脚本入口使用显式的 main(),既方便测试,也避免模块被导入时直接执行业务:

代码块 Python
1
2
3
4
5
6
7
8
def main() -> None:
    print("程序开始")


if __name__ == "__main__":
    main()

# 作为脚本运行时输出:程序开始

常见导入形式:

代码块 Python
1
2
3
4
5
6
7
8
import math
from pathlib import Path

print(math.sqrt(81))
print(Path.cwd())

# 输出:9.0
# 示例输出:/Users/you/project,实际值取决于启动目录

避免 from module import *,它会让名称来源不清晰。模块名也不要取成 random.py、json.py、typing.py 等标准库名称, 否则可能遮蔽真正的模块。

14. 类与数据类

类把状态和行为组织在一起。不是所有数据都需要类:简单转换可用函数,固定的小型记录可考虑元组或数据类。

代码块 Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from dataclasses import dataclass
from decimal import Decimal


@dataclass
class Product:
    name: str
    price: Decimal
    stock: int = 0

    def reserve(self, quantity: int) -> None:
        if quantity <= 0:
            raise ValueError("数量必须大于零")
        if quantity > self.stock:
            raise ValueError("库存不足")
        self.stock -= quantity


product = Product("Keyboard", Decimal("399.00"), stock=10)
product.reserve(2)
print(product.stock)

# 输出:8

@dataclass 可以生成初始化、相等性比较和展示等样板代码,但默认不会生成大小排序方法,也不会自动做业务校验。

继承表达“是一个”的关系,组合表达“拥有一个”的关系。业务代码通常优先组合,只有确实需要共享接口和可替换行为时再使用继承。

15. 迭代器与生成器

可迭代对象能产生迭代器,迭代器通过 __next__() 逐个返回值,耗尽时抛出 StopIteration。日常代码通常使用 for,无需手动调用这些方法。

包含 yield 的函数是生成器函数,调用后返回生成器对象,执行会在迭代时按需推进。假设 notes.txt 依次包含“第一行”、空行和 “第二行”:

代码块 Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def read_non_empty_lines(path: str):
    with open(path, "r", encoding="utf-8") as file:
        for line in file:
            stripped = line.strip()
            if stripped:
                yield stripped


for line in read_non_empty_lines("notes.txt"):
    print(line)

输出:

代码块 Text
1
2
# 第一行
# 第二行

生成器适合流式处理,但它通常只能消费一次,而且文件等资源的生命周期会随迭代过程延长。需要重复遍历时应重新创建生成器,或在数据量 允许时显式转成列表。

16. 类型标注

类型标注提高可读性,并帮助编辑器和静态类型检查器发现问题;Python 运行时不会根据类型注解强制检查它们。

代码块 Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
type UserId = int


def active_names(users: dict[UserId, str], disabled: set[UserId]) -> list[str]:
    return [name for user_id, name in users.items() if user_id not in disabled]


print(active_names({1: "Ada", 2: "Guido", 3: "Linus"}, {2}))

# 输出:['Ada', 'Linus']

这里的 type 别名语句来自 Python 3.12,因此适用于本文最低 Python 3.13 的基线。现代代码可直接写 list[str]、dict[str, int], 可空类型写成 User | None。类型别名不是新的运行时类型,UserId 仍然是 int;若业务需要区分不同标识符,必须另行设计。类型标注应表达真实契约, 不要为了“全部有类型”而使用大量 Any 或复杂泛型。

如果运行时确实需要验证外部输入,仍应写解析与校验逻辑;静态类型检查不能替代边界校验。

17. 并发模型怎么选

先按任务性质选择模型,再做性能测试:

场景常见选择主要注意点
大量阻塞 I/O,已有同步库threading、ThreadPoolExecutor共享状态、锁、超时和取消
大量并发网络 I/O,调用链支持异步asyncio不要在事件循环中执行阻塞操作
默认启用 GIL 的 CPython 中,CPU 密集型纯 Python 计算multiprocessing、ProcessPoolExecutor进程启动、序列化和内存成本
向量计算、科学计算NumPy 等原生实现数据布局和库自身的线程策略

不要把“并发”与“并行”等同,也不要默认线程越多越快。共享可变状态会引入竞态;即使某个操作在当前 CPython 中看似原子,也不应把实现 细节当成业务同步协议。

Python 3.13 首次提供实验性的 free-threaded CPython 构建,可在关闭 GIL 的模式下运行线程;它不是普通 3.13 解释器的默认行为,C 扩展 兼容性和性能都需要测试。3.13 的 JIT 同样是实验能力,构建时才启用且默认关闭。二者都不是“升级后程序自动并行、自动变快”。

版本状态随后继续演进:从 3.14 起 free-threaded 构建获得官方支持,但仍是可选模式;到 3.15,JIT 仍是实验功能且默认关闭。

18. 调试、日志与测试

18.1 先读完整 traceback

traceback 最后一行给出异常类型和消息,上方调用栈说明错误如何到达现场。修复根因,不要只在最外层加一个宽泛的 try/except。

常见异常包括:

  • SyntaxError:语法无法解析。
  • IndentationError:缩进不合法。
  • NameError:名称不存在。
  • TypeError:操作或调用不接受当前类型。
  • ValueError:类型可以接受,但值不合法。
  • KeyError、IndexError:映射键或序列位置不存在。
  • FileNotFoundError:目标文件不存在。

临时观察变量可以使用 print(),交互调试可使用 breakpoint();长期运行程序应使用 logging,而不是散落的调试输出:

代码块 Python
1
2
3
4
5
6
import logging

logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s")
logging.info("任务开始")

# 独立运行时写入 stderr:INFO 任务开始

18.2 为行为写测试

标准库 unittest 可以直接起步,项目也可以选择第三方测试框架。测试应覆盖公开行为、边界和失败路径,而不是绑定内部实现细节。 把下面内容保存为 test_example.py:

代码块 Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import unittest


def add(left: int, right: int) -> int:
    return left + right


class AddTests(unittest.TestCase):
    def test_adds_two_integers(self) -> None:
        self.assertEqual(add(2, 3), 5)


if __name__ == "__main__":
    unittest.main()

# 结果:1 项测试通过,耗时因环境而异
代码块 Shell
1
python -m unittest -v test_example

示例输出(unittest 默认写入 stderr,耗时因环境而异):

代码块 Text
1
2
3
4
5
6
# test_adds_two_integers (test_example.AddTests.test_adds_two_integers) ... ok
#
# ----------------------------------------------------------------------
# Ran 1 test in 0.000s
#
# OK

19. Python 3.13 带来了什么

对基础学习最直观的变化是 Windows 及支持 curses 的类 Unix 终端默认使用新的交互式解释器:支持多行编辑和历史记录, 终端提示与 traceback 默认着色,错误提示也继续改进。

需要准确区分的 3.13 能力:

  • free-threaded CPython:实验性的可选构建,不是默认解释器“移除了 GIL”。
  • JIT:实验性、构建时选择、默认关闭;3.13 时性能收益仍有限。
  • 类型系统:类型参数支持默认值,新增 TypeIs、ReadOnly 等能力,主要服务静态类型工具。
  • 标准库清理:cgi、imghdr、telnetlib 等一批长期弃用模块被移除,升级旧项目应先查看移植说明。
  • locals() 语义明确化:调试器和动态执行工具更容易获得一致行为,普通入门代码不应依赖修改 locals()。

这些变化不会改写前面介绍的核心语法。升级项目时应运行测试、检查依赖支持范围,并阅读对应版本的 What’s New 与移植说明,不能只修改 版本号后假定兼容。

20. 一个可继续扩展的小程序

下面的命令行程序综合了路径、函数、类型标注、容器、异常和标准库。它逐行读取 UTF-8 文本,统计由英文字母和内部单引号组成的单词; 中文等没有空格边界的语言需要真正的分词工具,不能直接套用这个正则表达式:

代码块 Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
from collections import Counter
from pathlib import Path
import re
import sys


WORD_PATTERN = re.compile(r"[A-Za-z]+(?:'[A-Za-z]+)?")


def top_words(path: Path, limit: int = 10) -> list[tuple[str, int]]:
    counts: Counter[str] = Counter()
    with path.open("r", encoding="utf-8") as file:
        for line in file:
            counts.update(WORD_PATTERN.findall(line.lower()))
    return counts.most_common(limit)


def main() -> int:
    if len(sys.argv) != 2:
        print(f"用法:{Path(sys.argv[0]).name} <文本文件>", file=sys.stderr)
        return 2

    path = Path(sys.argv[1])
    try:
        results = top_words(path)
    except (OSError, UnicodeError) as exc:
        print(f"读取失败:{exc}", file=sys.stderr)
        return 1

    for word, count in results:
        print(f"{word:<20} {count:>6}")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

将程序保存为 word_count.py,并令 sample.txt 内容为 Python is simple. Python is readable.:

代码块 Shell
1
python word_count.py sample.txt

输出:

代码块 Text
1
2
3
4
# python                    2
# is                        2
# simple                    1
# readable                  1

继续练习时可以逐步加入 argparse 参数、停用词、JSON 输出和单元测试。每次只增加一个可验证的能力,比一次堆入框架更容易理解。

21. 学完基础后检查什么

  • 能解释名称绑定、可变对象以及 == / is 的差异。
  • 能为字符串、数值和结构化输入选择安全的转换方式,不对外部输入使用 eval()。
  • 能熟练使用列表、字典、集合、推导式、enumerate() 和 zip()。
  • 能写参数清晰的函数,避免可变默认值,并理解局部与外层作用域。
  • 能捕获具体异常,用 with 管理文件等资源。
  • 能创建虚拟环境,通过 python -m pip 管理当前项目依赖。
  • 能把代码拆成模块,使用 main() 入口,并为关键行为编写测试。
  • 能按 I/O 密集或 CPU 密集选择并发模型,不把实验性 free-threaded/JIT 当成默认加速。

参考资料

评论

使用 GitHub 登录后参与讨论,评论将公开保存在 GitHub。

阅读到这里时加载评论。