目录257
- 第一个Python程序
- 交互式模式 vs 脚本模式
- print() 函数
- 注释
- input() 函数
- 转义字符
- 代码风格(PEP 8)
- 常见错误
- 要点
- 基础语法
- 变量
- 数据类型概览
- 类型转换
- 运算符
- 缩进与代码块
- 标识符与关键字
- 多行语句与同行多语句
- 实用例子
- 常见错误
- 要点
- 数字类型和运算
- 整数 int
- 浮点数 float
- 复数 complex
- 算术运算符
- 数学函数
- random 模块
- 数字类型转换
- 实用例子
- 常见陷阱
- 要点
- 字符串详解
- 创建字符串
- 字符串操作
- 索引和切片
- 字符串方法
- 字符串不可变
- 实用例子
- 常见陷阱
- 要点
- 列表
- 创建列表
- 访问元素
- 修改列表
- 添加元素
- 删除元素
- 列表操作
- 遍历列表
- 嵌套列表(二维列表)
- 列表方法速查表
- 实用例子
- 常见错误
- 要点
- 元组
- 创建元组
- 访问与操作
- 元组不可变
- 元组解包
- 命名元组
- 元组 vs 列表
- 实用例子
- 常见陷阱
- 要点
- 字典
- 字典的特点
- 创建字典
- 访问元素
- 修改与删除
- 字典操作
- 遍历字典
- 字典推导式
- 嵌套字典
- defaultdict 与 OrderedDict
- 字典方法速查表
- 实用例子
- 常见陷阱
- 要点
- 集合
- 创建集合
- 增删查
- 集合运算
- 集合判断
- 集合推导式
- frozenset
- 遍历
- 实用例子
- 集合 vs 列表 vs 字典
- 常见陷阱
- 要点
- 条件判断
- if / if-else / if-elif-else
- 条件表达式
- 嵌套 if
- 三元表达式
- 真值测试
- match-case(Python 3.10+)
- 实用例子
- 常见错误
- 最佳实践
- 要点
- for循环
- 遍历 range
- 遍历各种对象
- zip / reversed / sorted
- 嵌套循环
- break
- continue
- for-else
- 实用例子
- 常见错误
- 要点
- while循环
- 基本形式
- while vs for
- break 与 continue
- while-else
- 无限循环
- while True 常见模式
- 嵌套 while
- 实战例子
- 常见错误
- 要点
- 推导式
- 列表推导式
- 字典推导式
- 集合推导式
- 生成器表达式
- 实战例子
- 推导式 vs 传统循环
- 常见陷阱
- 要点
- 函数基础
- 定义与调用
- 参数
- 返回值
- 文档字符串
- 函数调函数与递归
- 变量作用域
- 常见错误
- 实战例子
- 要点
- 函数进阶
- 可变参数
- 参数解包
- 仅限关键字与仅限位置参数
- 函数注解
- lambda 表达式
- 闭包
- 装饰器
- 内置高阶函数
- 偏函数
- 常见陷阱
- 实战例子
- 要点
- Lambda和高阶函数
- Lambda 基础语法
- Lambda 的使用场景
- map() - 映射
- filter() - 过滤
- reduce() - 归约
- 组合使用
- 其他高阶函数
- Lambda vs 普通函数
- 常见陷阱
- 实用例子
- 要点
- 模块和包
- 什么是模块
- 导入模块的方式
- 模块搜索路径(sys.path)
- 模块内省:__name__、dir()、help()
- 创建自己的模块
- 包(Package)
- 标准库常用模块速查
- 第三方包与 pip
- 虚拟环境 venv
- 常见错误与陷阱
- 要点
- 文件读写
- 打开文件
- 读取文件
- 写入文件
- 文件定位:seek() 与 tell()
- 文件路径处理
- 二进制文件
- 异常处理
- 实用例子
- 最佳实践
- 要点
- 异常处理
- try/except 基本语法与执行顺序
- 捕获多个异常
- try/except/else/finally
- 捕获所有异常
- 抛出异常:raise
- 常见内置异常类型
- 自定义异常
- assert 断言
- 安全类型转换与输入验证
- 上下文管理器
- 异常处理最佳实践
- 实战例子
- 常见陷阱
- 要点
- 面向对象基础
- 核心概念与定义
- __init__ 与初始化
- 属性与方法速查
- 私有属性与名称改写
- 魔术方法速查
- 例子
- 常见陷阱
- 要点
- 面向对象进阶
- 继承
- super()
- 多重继承与 MRO
- 多态与鸭子类型
- 封装与 property
- 抽象基类(abc)
- 例子
- 常见陷阱
- 设计原则速记
- 要点
- 常用内置模块
- 模块速查总表
- datetime · 日期时间
- collections · 专业容器
- random · 随机数
- itertools · 迭代工具
- functools · 函数工具
- pathlib · 现代路径
- os 与 sys · 系统接口
- 其他常用标准库
- 实用例子
- 常见陷阱
- 要点
- 正则表达式
- 元字符速查表
- 字符类
- 量词
- 锚点与边界
- 分组与捕获
- 预查(零宽断言)
- re 模块函数
- 标志 flags
- 常用模式示例
- 实用例子
- 常见陷阱
- 要点
- JSON和CSV处理
- JSON 处理
- CSV 处理
- 实用例子
- 常见陷阱
- 最佳实践
- 要点
Python基础
第一个Python程序
交互式模式 vs 脚本模式
>>> print("你好") # 交互式:输入一行,立即执行一行
你好
>>> 1 + 1
2
>>> exit() # 退出- 交互式:命令行输入
python(macOS/Linux 用python3)进入>>>;适合试算、验证语法,代码不保存。 - 脚本模式:代码写入
hello.py,用python hello.py运行;代码可保存、修改、分享,是正式写法。
# hello.py
print("Hello, World!")
print("你好,世界!")print() 函数
基本用法
print("Hello") # 输出:Hello
print(123) # 输出:123
print("我", "爱", "Python") # 输出:我 爱 Python(多值默认空格分隔)
print(1 + 2 + 3) # 输出:6(先求值再打印)
name, age = "小红", 18
print("我叫", name) # 输出:我叫 小红文本必须加引号(单双引号等价),数字不用。
sep 与 end
print("A", "B", "C") # 输出:A B C(默认 sep=" ")
print("A", "B", "C", sep="-") # 输出:A-B-C
print("A", "B", "C", sep="") # 输出:ABC
print(2024, 1, 15, sep="/") # 输出:2024/1/15
print("第一行") # 默认 end="\n",打印后换行
print("第一行", end="") # 不换行
print("苹果", end=", ") # 用自定义结尾拼接
print("香蕉") # 输出:苹果, 香蕉
print("加载中", end=""); print(".", end=""); print(".") # 输出:加载中..格式化输出
name, age, score = "张三", 25, 95.5
print(f"我叫{name},今年{age}岁,考了{score}分") # 推荐:f-string,Python 3.6+
print(f"1+1={1 + 1}") # {} 内可直接写表达式 → 1+1=2
print(f"平均分:{score / 3:.2f}") # :.2f 保留 2 位小数
print("我叫{},今年{}岁".format(name, age)) # str.format(),老写法
print("我叫{0},今年{1}岁".format(name, age)) # 可指定位置
print("我叫%s,今年%d岁" % (name, age)) # % 格式化,已不推荐f-string 格式规范
{值:[[填充]对齐][宽度][,][.精度][类型]}
name, value = "苹果", 1234.5678
print(f"{name:>10}|") # 右对齐,宽度 10 → ' 苹果|'
print(f"{name:<10}|") # 左对齐
print(f"{name:^10}|") # 居中对齐
print(f"{value:,.2f}") # 千分位 + 2 位小数 → 1,234.57
print(f"{0.256:.2%}") # 百分比(乘 100 加 %)→ 25.60%
print(f"{7:03d}") # 补零到 3 位 → 007
print(f"{value:.2e}") # 科学计数法 → 1.23e+03
print(f"{value=}") # Python 3.8+:调试用,输出 'value=1234.5678'
print(f"{name!r}") # !r 调用 repr() → '苹果'注释
# 单行注释:# 之后的内容被忽略
age = 18 # 也可以在代码后面注释
"""
三引号包裹的是多行字符串,常作模块/函数/类的文档字符串(docstring)
"""
def calculate(radius):
"""计算圆面积:π × r²(函数文档)"""
return 3.14159 * radius ** 2
# TODO: 待实现的功能
# FIXME: 已知问题注释写“为什么这么做”和注意事项,不写“这行代码在干什么”;代码本身应足够清晰。
input() 函数
name = input("请输入你的名字:") # 显示提示并等待输入,返回字符串
print(f"你好,{name}")
age = input("你几岁?")
print(type(age)) # <class 'str'>:input() 永远返回字符串
# age + 1 → TypeError
age = int(input("你几岁?")) # 需要数字时显式转换
price = float(input("价格是多少?"))类型转换:int() 转整数、float() 转浮点数、str() 转字符串。
综合示例:
print("=== 自我介绍小程序 ===")
name = input("你叫什么名字?")
age = int(input("你几岁了?")) # 需要参与比较/运算时立刻转换
city = input("你住在哪个城市?")
print(f"\n姓名:{name}")
print(f"年龄:{age}岁")
print(f"城市:{city}")
print(f"\n{name},你好!欢迎来自{city}的{age}岁的你!")转义字符
print("第一行\n第二行") # \n 换行
print("姓名\t年龄\t城市") # \t 制表符,用于对齐
print("他说:\"你好!\"") # \" 在双引号串内输出引号
print('She said: "Hello!"') # 或直接用单引号包裹
print("C:\\Users\\Desktop") # \\ 表示一个反斜杠
print(r"C:\Users\Desktop") # r 前缀:原始字符串,不转义代码风格(PEP 8)
# 推荐
user_name = "小明" # 变量小写 + 下划线(snake_case)
total_score = 95
PI = 3.14159 # 常量全大写
result = 10 + 20 # 运算符两侧加空格
print(1, 2, 3) # 逗号后加空格
# 不推荐
totalScore = 95 # 驼峰是 Java 风格
result=10+20 # 太挤命名规则:用英文单词不用拼音、多词用下划线连接、变量名小写常量全大写、名字要能自解释。
常见错误
print(Hello)→NameError:Hello被当作未定义的变量名 → 字符串必须加引号。- 使用中文标点(
,。())→SyntaxError→ 一律改用英文标点。 IndentationError→ 不该缩进的缩进了,或同一层缩进不一致 → 同层代码对齐,统一 4 个空格,不混用 Tab。age = input(); age + 1→TypeError:input 返回字符串 → 用int()/float()转换后再运算。
要点
input()永远返回字符串,要做数值运算先int()/float()转换。print()用sep控制分隔符、end控制结尾,默认分别是空格和换行。- 格式化优先用 f-string:
f"{x:.2f}"控制小数位,{}内可直接写表达式。 - 缩进表示代码块,统一 4 个空格;Python 只认英文标点。
- 变量用 snake_case、常量全大写,名字要能自解释。
基础语法
变量
命名规则
user_name = "张三" # 只能含字母、数字、下划线
age2 = 20 # 可以含数字,但不能以数字开头
_private = "私有" # 可以下划线开头(约定表示内部使用)
# 2age = 20 # 非法:以数字开头
# user-name = "张三" # 非法:连字符会被当成减号
# for = 10 # 非法:关键字不能作变量名- 只能包含字母、数字、下划线;不能以数字开头;区分大小写(
name与Name是两个变量)。 - 不能用关键字命名;风格上用 snake_case,常量全大写,名字要能自解释。
赋值与解包
x = 10
a = b = c = 0 # 链式赋值:三个变量都是 0
x, y, z = 1, 2, 3 # 元组解包:x=1, y=2, z=3
name, age = "小红", 18
a, b = 10, 20
a, b = b, a # 交换变量:先算右侧 (b, a),再赋给左侧,无需临时变量
print(a, b) # 输出:20 10动态类型
x = 10 # int
x = "Hello" # 现在变成 str
x = [1, 2, 3] # 现在变成 list
print(type(x)) # <class 'list'>:type() 查看类型变量只是对象的名字,类型由值决定、可随时改变;实际编码中应让变量保持同一类型,避免类型不匹配的错误。
数据类型概览
| 类型 | 写法 | 特点 |
|---|---|---|
int |
18、-5 |
整数 |
float |
99.9、-10.5 |
带小数点的数 |
bool |
True、False |
本质是整数,True == 1、False == 0 |
str |
"小明"、'你好'、"""多行""" |
文本,不可变 |
list |
[1, "a", 3.14, True] |
方括号,可混合类型,可增删改 |
tuple |
(10, 20) |
圆括号,不可变,常用于固定结构 |
dict |
{"name": "张三", "age": 25} |
花括号键值对,按键查找 |
set |
{1, 2, 3} |
花括号无冒号,自动去重,支持交并差 |
name = "小明" # 双引号
message = '你好' # 单引号,与双引号等价
multi = """这是
多行字符串"""
fruits = ["苹果", "香蕉", "橙子"]
point = (10, 20) # 坐标点
person = {"name": "张三", "age": 25}
unique = {1, 2, 3, 3} # {1, 2, 3}:自动去重age = input("年龄:")
if age.isdigit(): # 先检查是否为纯数字字符串,再转换
age = int(age)
print(f"你的年龄是{age}岁")
else:
print("请输入数字!")类型转换
int("18") # 18
int(3.9) # 3:int() 直接截断小数,不是四舍五入(四舍五入用 round())
int(True) # 1
float("99.9") # 99.9
float(10) # 10.0
str(18) # "18":拼接字符串前必须转换,或用 f-string
str([1, 2, 3]) # "[1, 2, 3]"布尔转换规则——假值只有这些,其余都是 True:
bool(0) # False(0、0.0 都是 False,-1 等负数为 True)
bool("") # False("0" 是字符串,为 True)
bool([]) # False(空 list / tuple / dict / set 都是 False)
bool(None) # False
price = float(input("价格:")) # 输入 "99.9" → 99.9
quantity = int(input("数量:")) # 输入 "3" → 3
print(f"总价:{price * quantity}")
age = 18
message = "我今年" + str(age) + "岁" # 字符串拼接前必须 str(),用 f-string 更省事
name = input("姓名:")
if name: # 非空字符串即真,等价 if name != ""
print(f"你好,{name}!")运算符
算术运算符
print(10 + 3) # 13
print(10 - 3) # 7
print(10 * 3) # 30
print(10 / 3) # 3.3333...:除法结果总是 float
print(10 // 3) # 3:整除,向下取整
print(10 % 3) # 1:取余,常用于判断奇偶、能否整除
print(2 ** 3) # 8:幂运算比较运算符
5 == 5 # True:== 是比较,= 是赋值,不要混用
5 != 3 # True
5 > 3 # True
5 >= 5 # True逻辑运算符
True and False # False:两边都真才真
True or False # True:一边为真即真
not True # False:取反
age, has_id = 20, True
can_enter = age >= 18 and has_id # 输出:True(年满18且持有证件可入场)
score = 85
if 80 <= score < 90: # 链式比较,等价 score >= 80 and score < 90
print("良好")赋值运算符
x = 10
x += 5 # x = x + 5 → 15
x -= 3 # 12
x *= 2 # 24
x /= 4 # 6.0
x //= 2 # 3.0
x %= 2 # 1.0
count = 0
count += 1 # 计数器的标准写法运算符速查
| 类别 | 运算符 | 说明 |
|---|---|---|
| 算术 | + - * / |
加减乘除,/ 结果总是 float |
| 算术 | // % ** |
整除(向下取整)、取余、幂 |
| 比较 | == != > < >= <= |
返回 bool;== 比较、= 赋值 |
| 逻辑 | and or not |
短路求值,返回参与运算的值而非布尔 |
| 赋值 | = += -= *= /= //= %= **= |
复合赋值等价于 x = x op y |
缩进与代码块
Python 用缩进(而非 {})表示代码块,同一缩进级别的代码属于同一个块。
age = 18
if age >= 18:
print("你成年了") # 属于 if 块
print("可以投票") # 仍属于 if 块
print("程序结束") # 缩进回退,总会执行
for i in range(3):
print(f"第{i}次") # 属于循环体- 推荐 4 个空格(PEP 8);不要混用空格与 Tab;同一层必须对齐。
if、for、while、def、class等语句以:结尾,其下必须缩进,否则IndentationError: expected an indented block。
标识符与关键字
import keyword
print(keyword.kwlist)
# if, else, elif, for, while, break, continue, def, class, return,
# import, from, as, try, except, finally, raise, with, lambda,
# in, is, not, and, or, pass, global, nonlocal, del, yield, assert,
# True, False, None
if_condition = 10 # 想用关键字含义作名字时,加下划线或改名
class_name = "A"多行语句与同行多语句
# 括号内可直接换行(推荐)
total = (1 + 2 + 3 + 4 +
5 + 6 + 7 + 8)
fruits = ["苹果", "香蕉",
"橙子", "葡萄"]
# 反斜杠续行:\ 之后不能有任何字符(包括空格和注释)
total = 1 + 2 + \
3 + 4
x = 10; y = 20 # 分号可在一行写多条语句,但不推荐,可读性差实用例子
例子1:交换变量
a, b = 100, 200
a, b = b, a # 一行完成交换,无需临时变量
print(a, b) # 输出:200 100例子2:输入校验
try:
age = int(input("请输入年龄:"))
print(f"你的年龄是{age}岁")
except ValueError: # 输入非数字时 int() 抛 ValueError
print("请输入有效的数字!")例子3:解包赋值
person = ("张三", 25, "北京")
name, age, city = person # 按位置解包
print(f"{name},{age}岁,来自{city}")
name, age, _, job = ("李四", 30, "上海", "程序员") # _ 接收并忽略不需要的值
name, age, *others = ("王五", 28, "广州", "设计师", "本科")
print(name, age) # 输出:王五 28
print(others) # 输出:['广州', '设计师', '本科']:* 收集剩余值常见错误
NameError: name 'username' is not defined:变量名拼写不一致(user_name≠username)→ 检查拼写。IndentationError:if/for/def后没缩进、多缩进或同层缩进不一致 → 统一 4 空格,不混 Tab。TypeError: 不能把字符串和数字相加:input()返回字符串 → 用int()/float()转换后再运算。SyntaxError于if age = 18:=是赋值、==是比较 → 条件判断里用==。
要点
- 变量是对象的名字:类型随值变化,命名用 snake_case、常量全大写,避开关键字。
a, b = b, a交换、x, y = 1, 2解包、_忽略、*rest收集剩余值。- 缩进即代码块,统一 4 个空格,
:之后必须缩进。 ==比较、=赋值;/得 float、//整除向下取整、%取余、**幂。int()截断而非四舍五入(用round());假值为0、""、空容器、None、False。- 长表达式用括号换行,一行只写一条语句。
数字类型和运算
整数 int
age = 18
temperature = -10
big = 123456789012345678901234567890 # Python 的 int 无大小限制,不会溢出
huge = 10 ** 100 # 1 后面 100 个 0进制与转换
0b1010 # 二进制,等于 10
0o12 # 八进制,等于 10
0xFF # 十六进制,等于 255(常用于颜色代码、内存地址)
num = 255
bin(num) # '0b11111111'
oct(num) # '0o377'
hex(num) # '0xff'
int('0b11111111', 2) # 255:int(字符串, 进制) 转回十进制
int('0o377', 8) # 255
int('FF', 16) # 255(可省略 0x)
color = "FF0000"
r, g, b = int(color[0:2], 16), int(color[2:4], 16), int(color[4:6], 16)
print(f"RGB: ({r}, {g}, {b})") # 输出:RGB: (255, 0, 0)浮点数 float
price = 99.9
big = 1.5e3 # 1500.0:e 表示 10 的幂
small = 1.5e-3 # 0.0015精度问题
十进制小数大多无法用二进制精确表示,因此浮点运算存在误差:
print(0.1 + 0.2) # 0.30000000000000004
print(0.1 + 0.2 == 0.3) # False:不要用 == 比较浮点数三种处理方式:
abs((0.1 + 0.2) - 0.3) < 1e-9 # True:用误差范围比较(推荐)
round(0.1 + 0.2, 1) == 0.3 # True:round 到相同位数再比较
from decimal import Decimal # 精确十进制,用于金额等场景
Decimal('19.9') + Decimal('29.9') # Decimal('49.8'),精确无误差| 场景 | 选择 |
|---|---|
| 金钱、金融、会计(必须精确) | Decimal(构造时传字符串) |
| 科学计算、一般小数、性能敏感 | float(Decimal 更慢) |
复数 complex
c = 3 + 4j # Python 用 j 表示虚数单位
c.real # 3.0
c.imag # 4.0
(1 + 2j) * (3 + 4j) # (-5+10j)算术运算符
10 / 3 # 3.3333333333333335:/ 总是返回 float,即使能整除(10 / 2 → 5.0)
10 // 3 # 3:整除,向下取整
-10 // 3 # -4:向下取整(-3.5 → -4),不是向零取整
10 % 3 # 1:取余
divmod(10, 3) # (3, 1):同时得到商和余数
2 ** 3 # 8:幂运算
9 ** 0.5 # 3.0:开平方
pow(2, 3) # 8:等价 2 ** 3
pow(2, 3, 5) # 3:模幂,等价 (2 ** 3) % 5
# 优先级(高→低):() > ** > * / // % > + -
2 + 3 * 4 ** 2 / 8 - 1 # 7.0;不确定时一律加括号常见用法:
num % 2 == 0 # 判断偶数
num % 5 == 0 # 判断能否被 5 整除
total_seconds = 3665
hours = total_seconds // 3600
minutes = (total_seconds % 3600) // 60
seconds = total_seconds % 60
print(f"{hours}小时{minutes}分钟{seconds}秒") # 1小时1分钟5秒
future = 10000 * (1 + 0.05) ** 10 # 复利:本金 × (1 + 利率)^年数数学函数
内置函数
abs(-5) # 5
round(3.14159) # 3:默认取整
round(3.14159, 2) # 3.14:保留 2 位小数
max(1, 5, 3) # 5
min(1, 5, 3) # 1
sum([1, 2, 3]) # 6
scores = [85, 92, 78, 96, 88]
average = sum(scores) / len(scores)
print(f"最高分:{max(scores)},最低分:{min(scores)},平均分:{average:.2f}")math 模块
import math
math.pi # 3.141592653589793
math.e # 2.718281828459045
math.ceil(3.14) # 4:向上取整
math.floor(3.99) # 3:向下取整
math.sqrt(16) # 4.0
math.pow(2, 3) # 8.0(返回 float)
math.log(10) # 自然对数,以 e 为底
math.log10(100) # 2.0
math.sin(math.pi/2) # 1.0:三角函数参数是弧度
math.radians(90) # 角度转弧度 → 1.5707...
math.degrees(math.pi) # 弧度转角度
math.factorial(5) # 120
# 两点间距离
distance = math.sqrt((3 - 0)**2 + (4 - 0)**2) # 5.0| 函数 / 常数 | 说明 |
|---|---|
math.pi、math.e |
圆周率、自然常数 |
math.ceil(x) / math.floor(x) |
向上 / 向下取整,返回 int |
math.sqrt(x) |
平方根,返回 float |
math.pow(x, y) |
幂运算,返回 float(等价于 x ** y) |
math.log(x) / math.log(x, base) / math.log10(x) |
自然对数 / 指定底 / 常用对数 |
math.sin/cos/tan(x) |
三角函数,参数单位为弧度 |
math.radians(d) / math.degrees(r) |
角度与弧度互换 |
math.factorial(n) |
阶乘 |
random 模块
import random
random.random() # [0.0, 1.0) 随机浮点数
random.randint(1, 10) # [1, 10] 随机整数,两端都包含
random.uniform(1.0, 10.0) # [1.0, 10.0] 随机浮点数
random.choice(['red', 'green', 'blue']) # 从序列随机选一个
random.shuffle(numbers) # 原地打乱列表
random.sample(range(1, 46), 6) # 不重复抽 6 个
import string
''.join(random.choices(string.ascii_letters + string.digits, k=8)) # 8 位随机密码| 函数 | 说明 |
|---|---|
random.random() |
[0.0, 1.0) 随机浮点数 |
random.randint(a, b) |
[a, b] 随机整数,两端都包含 |
random.uniform(a, b) |
[a, b] 随机浮点数 |
random.choice(seq) |
从非空序列随机取一个元素 |
random.choices(seq, k=n) |
有放回抽 n 个,返回列表 |
random.sample(seq, k) |
无重复抽 k 个 |
random.shuffle(lst) |
原地打乱列表,返回 None |
random.seed(n) |
设定随机种子,使结果可复现 |
数字类型转换
int(3.9) # 3:截断,不是四舍五入
int("123") # 123
int("0xFF", 16) # 255
float(10) # 10.0
float("3.14") # 3.14
round(3.9) # 4:四舍五入用 round()
10 + 3.5 # 13.5:int 与 float 混合运算,结果自动提升为 float实用例子
例子1:贷款月供(等额本息)
loan = float(input("贷款金额(元):"))
years = int(input("贷款年限:"))
rate = float(input("年利率(%):")) / 100
monthly_rate = rate / 12
months = years * 12
monthly_payment = loan * monthly_rate * (1 + monthly_rate) ** months / \
((1 + monthly_rate) ** months - 1)
print(f"月供:{monthly_payment:.2f}元")
print(f"总还款:{monthly_payment * months:.2f}元")
print(f"总利息:{monthly_payment * months - loan:.2f}元")例子2:单位转换工具
print("1. 千克转磅 2. 千米转英里 3. 摄氏度转华氏度")
choice = int(input("选择(1-3):"))
if choice == 1:
kg = float(input("输入千克:"))
print(f"{kg}千克 = {kg * 2.20462:.2f}磅")
elif choice == 2:
km = float(input("输入千米:"))
print(f"{km}千米 = {km * 0.621371:.2f}英里")
elif choice == 3:
c = float(input("输入摄氏度:"))
print(f"{c}°C = {c * 9/5 + 32:.1f}°F")例子3:掷骰子
import random
input("按回车掷骰子...")
die1, die2 = random.randint(1, 6), random.randint(1, 6)
total = die1 + die2
print(f"第一个骰子:{die1}\n第二个骰子:{die2}\n总点数:{total}")
if total in (7, 11):
print("你赢了!")
elif total in (2, 3, 12):
print("你输了!")
else:
print("再来一次!")常见陷阱
-7 // 2得-4不是-3://是向下取整(向负无穷),正数表现为去尾,负数会更小。0.1 + 0.2 == 0.3为False:二进制无法精确表示十进制小数 → 用abs(a-b) < 1e-9或round()比较,金额用Decimal。ZeroDivisionError: division by zero:除以 0 直接报错 → 先判断除数是否为 0 再除,或封装safe_divide(a, b)返回None。int(3.9)得3:int()是截断不是四舍五入 → 要四舍五入用round(),向上/向下取整用math.ceil()/math.floor()。
要点
int无大小限制;float有精度误差,金额用Decimal。/总返回 float;//向下取整(负数注意);%取余;divmod()同时拿商和余;**幂。- 优先级:
()>**>* / // %>+ -,不确定就加括号。 int()截断、round()四舍五入、math.ceil/floor上下取整。- 浮点数比较用误差范围,绝不用
==;除法前检查除数是否为 0。 math提供常数与高级函数(参数为弧度),random生成随机数与抽样。
字符串详解
创建字符串
单引号和双引号
单双引号完全等价,选哪种取决于字符串内部含哪种引号,避免转义。
name = 'Python'
message = "Hello, World!"
text1 = "I'm a student" # 内含单引号 → 用双引号包
text2 = 'He said "Hello"' # 内含双引号 → 用单引号包三引号(多行字符串)
保留换行与全部格式,常用于 docstring、模板文本。
poem = '''
床前明月光,
疑是地上霜。
'''
doc = """
这是多行字符串
保留所有格式
"""转义字符
| 转义 | 含义 |
|---|---|
\n |
换行 |
\t |
制表符(Tab) |
\\ |
反斜杠本身 |
\' \" |
引号 |
\r |
回车 |
\b |
退格 |
\0 |
空字符 |
print("第一行\n第二行")
print("姓名\t年龄\t城市")
print("C:\\Users\\Desktop") # 路径中的反斜杠必须写成 \\原始字符串
前缀 r/R 使反斜杠不转义,用于 Windows 路径和正则表达式。
path = r"C:\new\test.txt" # \n 不会被转义成换行
pattern = r"\d+\.\d+" # 匹配小数字符串操作
拼接与重复
full_name = "张" + "三" # 张三
laugh = "哈" * 5 # 哈哈哈哈哈
# "我今年" + 18 + "岁" → TypeError
message = "我今年" + str(18) + "岁" # 非字符串须先 str()格式化:f-string(推荐)
字符串前加 f,{} 内可直接写变量或表达式。
name, age, score = "小明", 18, 95.5
print(f"我叫{name},今年{age}岁,考了{score}分")
print(f"明年我{age + 1}岁")
print(f"平均分:{(90 + 95 + 88) / 3:.2f}") # 保留2位小数
print(f"|{name:10}|") # 默认左对齐,宽度10
print(f"|{name:<10}|") # 左对齐
print(f"|{name:>10}|") # 右对齐
print(f"|{name:^10}|") # 居中
print(f"|{name:*^10}|") # 用 * 填充格式化:str.format()
"我叫{},今年{}岁".format("小红", 18) # 按位置
"我叫{0},今年{1}岁,{0}很高兴".format("小红", 18) # 指定索引
"我叫{name},今年{age}岁".format(name="小红", age=18) # 指定名称格式化:%(旧写法)
"我叫%s,今年%d岁,考了%.1f分" % (name, age, score) # %s字符串 %d整数 %f浮点| 占位符 | 含义 |
|---|---|
%s |
字符串(任何对象都可) |
%d |
十进制整数 |
%f |
浮点数,%.1f 保留 1 位 |
%x |
十六进制整数 |
%% |
字面量 % |
只在阅读旧代码时需要,新代码一律用 f-string。
索引和切片
索引
正向从 0 开始,负向从 -1 开始(-1 是最后一个),越界抛 IndexError。
text = "Python"
print(text[0], text[5]) # P n
print(text[-1], text[-6]) # n P切片
[起始:结束:步长],左闭右开(不含结束位置)。
text = "Hello, World!"
print(text[0:5]) # Hello
print(text[:5]) # Hello(省略起始=从头)
print(text[7:]) # World!(省略结束=到尾)
print(text[:]) # Hello, World!(整体复制)
print(text[::2]) # Hlo ol!(步长2)
print(text[::-1]) # !dlroW ,olleH(反转)常用切片技巧
text = "Python Programming"
text[:5] # Pytho 取前5个
text[-5:] # mming 取后5个
text[1:-1] # ython Programmin 去掉首尾字符
text[::-1] # gnimmargorP nohtyP 反转字符串方法
所有方法都返回新字符串,不修改原字符串。
大小写转换
text = "Hello World"
text.upper() # HELLO WORLD
text.lower() # hello world
text.capitalize() # Hello world(首字母大写)
text.title() # Hello World(每个单词首字母大写)
text.swapcase() # hELLO wORLD(互换)查找和替换
text = "Hello, World! Hello, Python!"
text.find("World") # 7,找不到返回 -1
text.index("World") # 7,找不到抛 ValueError
text.count("Hello") # 2
text.replace("Hello", "Hi") # 全部替换
text.replace("Hello", "Hi", 1) # 只替换前1个判断
"hello.py".startswith("hello") # True
"hello.py".endswith(".py") # True
"llo" in "hello.py" # True(包含)
"abc" not in "hello.py" # True
"123".isdigit() # True(全数字)
"abc".isalpha() # True(全字母)
"abc123".isalnum() # True(字母或数字)
" ".isspace() # True(全空白)
"hello".islower() # True
"HELLO".isupper() # True分割和连接
"apple,banana,orange".split(",") # ['apple', 'banana', 'orange']
"第一行\n第二行".splitlines() # ['第一行', '第二行'],自动处理 \r\n
",".join(['apple', 'banana']) # apple,banana
" | ".join(['apple', 'banana']) # apple | banana去除空白
" Hello ".strip() # "Hello"(两端)
" Hello ".lstrip() # "Hello "(左端)
" Hello ".rstrip() # " Hello"(右端)
"***Hello***".strip("*") # "Hello"(可指定字符)对齐
text = "Python"
text.center(20) # " Python "
text.center(20, "*") # "*******Python*******"
text.ljust(20, "-") # "Python--------------"
text.rjust(20, "-") # "--------------Python"方法速查表
| 分类 | 方法 |
|---|---|
| 大小写 | upper lower capitalize title swapcase |
| 查找替换 | find rfind index rindex count replace |
| 判断 | startswith endswith isdigit isalpha isalnum isspace isupper islower |
| 分割连接 | split rsplit splitlines join |
| 去空白 | strip lstrip rstrip |
| 对齐 | center ljust rjust |
字符串不可变
创建后不能修改单个字符,任何“修改”都是生成新字符串(因此可安全用作字典键)。
text = "Hello"
# text[0] = "h" # TypeError
text = "h" + text[1:] # hello
text = text.replace("H", "h")实用例子
密码强度验证
password = input("请输入密码:")
if len(password) < 8:
print("密码太短,至少8位")
elif not any(c.isdigit() for c in password):
print("密码必须包含数字")
elif not any(c.isalpha() for c in password):
print("密码必须包含字母")
elif password.islower() or password.isupper():
print("密码必须包含大小写字母")
else:
print("密码强度:强")格式化输出表格
students = [("张三", 90, 85, 92), ("李四", 88, 92, 87), ("王五", 95, 89, 91)]
print("姓名\t语文\t数学\t英语\t平均分")
print("-" * 40)
for name, chinese, math, english in students:
avg = (chinese + math + english) / 3
print(f"{name}\t{chinese}\t{math}\t{english}\t{avg:.1f}")敏感词过滤
text = "这是一条包含敏感词的评论"
for word in ["敏感词", "不良信息", "违规"]:
text = text.replace(word, "*" * len(word))
print(text) # 这是一条包含***的评论提取文件名与扩展名
filename = "document.pdf"
name, ext = filename.rsplit(".", 1) # rsplit 从右侧分割,适应多圆点文件名
dot = filename.rfind(".") # 等价写法
name, ext = filename[:dot], filename[dot+1:]简单文本分析
text = input("输入一段文字:")
print(f"字符总数:{len(text)}")
print(f"单词数量:{len(text.split())}") # 无参 split 按任意空白切分
print(f"字母数量:{sum(c.isalpha() for c in text)}")
print(f"数字数量:{sum(c.isdigit() for c in text)}")
print(f"空格数量:{text.count(' ')}")常见陷阱
- 试图修改字符:
text[0] = "h"→TypeError;改用切片拼接或replace()生成新串。 - 非字符串拼接:
"年龄" + 18→TypeError;先str(18)或用 f-string。 - 三引号缩进:缩进空格会被计入字符串,不需要时用
"""...""".strip()。 findvsindex:不确定子串是否存在时用find(),index()找不到会抛异常。- 字符串乘法
"=" * 50可快速生成分隔线。 join参数顺序:",".join(列表)是“分隔符. join(序列)”,与split的"a,b".split(",")相反,写反会报TypeError。split()与split(","):无参split()按任意连续空白切分并忽略首尾空串,适合切词;指定分隔符时不会合并连续分隔符。
要点
- 单双引号等价;三引号保留格式;
r""取消转义(路径、正则)。 - 索引正向从 0、负向从 -1;切片
[起:止:步]左闭右开,[::-1]反转。 - 格式化优先 f-string,
{}内可写表达式,支持:.2f、:>10、:*^10等格式说明符。 - 字符串方法全部返回新字符串,不改动原串;
find返回 -1,index抛异常。 - 字符串不可变:修改只能重建,因此可哈希、可作字典键。
- 去空白用
strip/lstrip/rstrip;分割用split/splitlines,反向用rsplit/rfind,连接用sep.join(列表)。
列表
列表(list)是有序、可变、可重复的元素序列,元素可以是任意类型。
empty_list = []
numbers = [1, 2, 3, 4, 5]
mixed = [1, "hello", 3.14, True, [1, 2, 3]] # 支持混合类型创建列表
fruits = ["苹果", "香蕉", "橙子"] # 方括号直接创建
numbers = list(range(1, 6)) # [1, 2, 3, 4, 5]
chars = list("Python") # ['P', 'y', 't', 'h', 'o', 'n']
squares = [x**2 for x in range(1, 11)] # 列表推导式,按规则生成列表推导式
[表达式 for 变量 in 可迭代对象 if 条件],比 for + append 更简洁高效。
squares = [x**2 for x in range(1, 11)] # [1, 4, 9, ..., 100]
evens = [x for x in range(20) if x % 2 == 0] # 带条件过滤
words = [" Apple ", "Banana"]
clean = [w.strip().lower() for w in words] # ['apple', 'banana']访问元素
索引
正向从 0 开始,负向从 -1 开始(-1 是最后一个),越界抛 IndexError。
fruits = ["苹果", "香蕉", "橙子", "葡萄"]
print(fruits[0], fruits[1]) # 苹果 香蕉
print(fruits[-1], fruits[-2]) # 葡萄 橙子切片
[起始:结束:步长],左闭右开,返回新列表(不修改原列表)。
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
numbers[2:5] # [2, 3, 4]
numbers[:3] # [0, 1, 2] 前3个
numbers[7:] # [7, 8, 9] 从索引7到末尾
numbers[:] # 整体浅拷贝
numbers[::2] # [0, 2, 4, 6, 8]
numbers[1::2] # [1, 3, 5, 7, 9]
numbers[::-1] # [9, 8, ..., 0] 反转修改列表
列表可变,这是与字符串最大的区别。
fruits = ["苹果", "香蕉", "橙子"]
fruits[0] = "西瓜" # ['西瓜', '香蕉', '橙子'],索引不可越界
numbers = [1, 2, 3, 4, 5]
numbers[1:3] = [20, 30] # [1, 20, 30, 4, 5]
numbers[1:3] = [9] # 新值长度可不同:[1, 9, 4, 5],列表随之缩短添加元素
fruits = ["苹果", "香蕉"]
fruits.append("橙子") # 末尾追加一个元素
fruits.insert(1, "西瓜") # 在索引1插入,其后元素后移;索引越界则插到末尾
fruits.extend(["葡萄", "梨"]) # 追加可迭代对象中的所有元素
fruits = fruits + ["芒果"] # + 生成新列表,原列表不变extend() 原地修改(更高效);+ 生成新列表(保留原列表时用)。
删除元素
fruits = ["苹果", "香蕉", "橙子", "葡萄"]
del fruits[1] # 按索引删除;del fruits[1:3] 删除切片
fruits.remove("香蕉") # 按值删除第一个匹配项,不存在抛 ValueError
last = fruits.pop() # 删除并返回末尾元素
first = fruits.pop(0) # 删除并返回指定索引元素
fruits.clear() # 清空内容,列表对象保留用 remove() 前先用 in 判断存在性可避免异常。
列表操作
查找
fruits = ["苹果", "香蕉", "橙子", "香蕉"]
fruits.index("香蕉") # 1,第一次出现的位置,不存在抛 ValueError
fruits.count("香蕉") # 2
"苹果" in fruits # True排序
numbers = [3, 1, 4, 1, 5, 9, 2]
numbers.sort() # 原地升序,返回 None
numbers.sort(reverse=True) # 原地降序
sorted_numbers = sorted(numbers) # 内置函数,返回新列表,原列表不变
students = [("张三", 90), ("李四", 85), ("王五", 95)]
students.sort(key=lambda s: s[1], reverse=True) # 按第2个元素(成绩)降序
sorted(["pear", "apple", "fig"], key=len) # ['fig', 'pear', 'apple']key 指定排序依据的函数,reverse=True 降序。
反转
fruits = ["苹果", "香蕉", "橙子"]
fruits.reverse() # 原地反转,返回 None
reversed_fruits = fruits[::-1] # 返回新列表,原列表不变其他操作
numbers = [1, 2, 3, 4, 5]
len(numbers) # 5
max(numbers), min(numbers), sum(numbers) # 5 1 15
copy1 = numbers.copy() # 浅拷贝
copy2 = numbers[:] # 浅拷贝
copy3 = list(numbers) # 浅拷贝复制:浅拷贝 vs 深拷贝
import copy
a = [1, [2, 3]]
b = a.copy() # 浅拷贝:外层独立,内层对象仍共用
b[1].append(4)
print(a) # [1, [2, 3, 4]] 内层被改动
c = copy.deepcopy(a) # 深拷贝:递归复制,完全独立
c[1].append(5)
print(a) # [1, [2, 3, 4]] 不受影响a.copy() / a[:] / list(a) 均为浅拷贝,只复制一层;需要完全独立时用 copy.deepcopy()。
遍历列表
fruits = ["苹果", "香蕉", "橙子"]
for fruit in fruits: # 只要元素值
print(fruit)
for i, fruit in enumerate(fruits): # 同时要索引和元素(推荐)
print(f"{i}: {fruit}")
for i in range(len(fruits)): # 需按下标修改元素时用
fruits[i] = fruits[i].upper()
for i, fruit in enumerate(fruits, 1): # 起始编号可指定,如从1开始
print(f"{i}. {fruit}")嵌套列表(二维列表)
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
print(matrix[1][2]) # 6,第一个索引是行,第二个是列
for row in matrix: # 外层遍历行,内层遍历列
for item in row:
print(item, end=" ")
print()列表方法速查表
| 操作 | 写法 |
|---|---|
| 添加 | append(x) 末尾追加;insert(i, x) 指定位置插入;extend(iter) 批量追加 |
| 删除 | remove(x) 按值;pop([i]) 弹出并返回;del lst[i]/del lst[a:b];clear() 清空 |
| 查找 | index(x) 首个匹配索引;count(x) 出现次数;x in lst 是否存在 |
| 排序 | sort(key=None, reverse=False) 原地;sorted(lst) 返回新列表 |
| 反转 | reverse() 原地;lst[::-1] 返回新列表 |
| 复制 | copy() / lst[:] / list(lst)(均浅拷贝);copy.deepcopy() 深拷贝 |
| 通用 | len max min sum |
实用例子
成绩统计
scores = []
while True:
score = input("输入成绩(输入q退出):")
if score == 'q':
break
scores.append(float(score))
if scores:
print(f"总分:{sum(scores)}")
print(f"平均分:{sum(scores) / len(scores):.2f}")
print(f"最高分:{max(scores)},最低分:{min(scores)}")去重(两种方法)
numbers = [1, 2, 2, 3, 3, 3, 4, 4, 5]
unique = list(set(numbers)) # 简单,但不保证顺序
unique = [] # 保持原顺序
for num in numbers:
if num not in unique:
unique.append(num)购物车(列表 + 字典配合)
cart = []
products = {1: ("苹果", 5.0), 2: ("香蕉", 3.0), 3: ("橙子", 4.0)}
while True:
for id, (name, price) in products.items():
print(f"{id}. {name} - ¥{price}")
choice = input("选择商品(输入0结账):")
if choice == "0":
break
id = int(choice)
if id in products:
cart.append(products[id])
total = sum(price for name, price in cart)
print(f"总计:¥{total}")待办事项(增删 + enumerate)
todo_list = []
while True:
print("\n=== 待办事项 ===")
print("1. 添加任务 2. 查看任务 3. 完成任务 4. 退出")
choice = input("选择操作:")
if choice == "1":
todo_list.append(input("输入任务:"))
print("任务已添加")
elif choice == "2":
if todo_list:
for i, task in enumerate(todo_list, 1):
print(f"{i}. {task}")
else:
print("暂无任务")
elif choice == "3":
num = int(input("完成第几个任务?")) - 1
if 0 <= num < len(todo_list):
print(f"完成:{todo_list.pop(num)}") # pop 返回被删元素
elif choice == "4":
break找出重复元素
numbers = [1, 2, 3, 2, 4, 3, 5, 1]
duplicates, seen = [], []
for num in numbers:
if num in seen and num not in duplicates:
duplicates.append(num)
seen.append(num)
print(duplicates) # [2, 3, 1]常见错误
- 索引越界:
lst[5]超出范围抛IndexError;访问前用len()判断或用try-except。 - 遍历中修改列表:边遍历边
remove()会跳元素,应改用列表推导式重建、遍历副本或倒序删除。
numbers = [1, 2, 3, 4, 5]
for num in numbers: # 错误:删除后索引前移,元素被跳过
if num % 2 == 0:
numbers.remove(num)
numbers = [1, 2, 3, 4, 5]
numbers = [n for n in numbers if n % 2 != 0] # 正确:重建新列表
for i in range(len(numbers) - 1, -1, -1): # 正确:倒序遍历删除
if numbers[i] % 2 == 0:
del numbers[i]- 赋值是引用不是复制:
b = a后改b会同步改a;用a.copy()/a[:]/list(a)。 - 浅拷贝不够:嵌套列表需
copy.deepcopy(),否则内层对象仍共享。 remove()值不存在抛ValueError:先if x in lst。sort()/reverse()返回None:lst = lst.sort()会得到None,它们是原地修改。
要点
- 列表有序、可变、可重复,元素可以是任意类型(包括列表)。
- 索引从 0 开始、负索引从 -1 开始;切片
[起:止:步]左闭右开,[::-1]反转,切片赋值可改变列表长度。 - 增:
append/insert/extend;删:remove(按值) /pop(按位并返回) /del/clear。 sort()、reverse()原地修改并返回None;sorted()、切片返回新列表。- 遍历取索引用
enumerate(lst, start);二维列表用lst[行][列]与嵌套循环。 b = a是引用,浅拷贝只复制一层,深层独立需deepcopy;不要边遍历边增删。
元组
元组(tuple)是不可变的有序序列,用于存放确定后不应改变的数据(坐标、配置、日期等);因此防误改、更省内存、访问更快、可作字典键。
创建元组
empty = () # 空元组,或 tuple()
mixed = (1, "hello", 3.14, True) # 支持混合类型
point = 1, 2, 3 # 括号可省略(不推荐)
single = (1,) # 单元素元组必须带逗号
print(type((1,))) # <class 'tuple'>;type((1)) 是 int,没逗号不算元组
tuple([1, 2, 3]) # (1, 2, 3),从列表转换
tuple("Python") # ('P', 'y', 't', 'h', 'o', 'n')
tuple(range(5)) # (0, 1, 2, 3, 4)访问与操作
索引、切片规则与列表完全一致(正向从 0、负向从 -1,切片左闭右开),只是结果仍是元组。
fruits = ("苹果", "香蕉", "橙子", "葡萄")
print(fruits[0], fruits[-1]) # 苹果 葡萄
print(fruits[1:3]) # ('香蕉', '橙子')
print(fruits[::2]) # ('苹果', '橙子')
t1, t2 = (1, 2, 3), (4, 5, 6)
t1 + t2 # (1, 2, 3, 4, 5, 6),拼接生成新元组
(1, 2) * 3 # (1, 2, 1, 2, 1, 2),重复
fruits.index("香蕉") # 首次出现的索引
fruits.count("香蕉") # 出现次数
"苹果" in fruits # True
len(fruits), max(fruits), min(fruits), sum(fruits) # 通用函数同样适用遍历同列表:for fruit in fruits,需索引用 enumerate(fruits)。
元组不可变
point = (10, 20)
# point[0] = 15 # TypeError: 不支持元素赋值
# point.append(30) # AttributeError: 无此方法
# del point[0] # TypeError: 不能删除元素元组本身不可变,但其中的可变对象内容可以改变:
t = ([1, 2], [3, 4])
# t[0] = [5, 6] # TypeError:不能替换元素
t[0].append(3) # ([1, 2, 3], [3, 4]),列表内部被修改元组解包
x, y = (10, 20) # 10 20
a, b = 1, 2
a, b = b, a # 交换变量,无需临时变量
numbers = (1, 2, 3, 4, 5)
first, *middle, last = numbers # first=1 middle=[2, 3, 4](列表) last=5
name, age, *_ = ("张三", 25, "北京", "程序员") # _ 表示忽略剩余值
def get_student():
return "小明", 18, 95 # 实为返回一个元组
name, age, score = get_student() # 函数多返回值靠元组解包接收命名元组
namedtuple 生成可用属性名访问的元组子类,兼具元组特性与可读性。
from collections import namedtuple
Student = namedtuple('Student', ['name', 'age', 'score'])
s = Student('小明', 18, 95)
print(s.name, s.age, s.score) # 小明 18 95,仍可用索引 s[0]
print(s._asdict()) # {'name': '小明', 'age': 18, 'score': 95}元组 vs 列表
| 特性 | 列表 | 元组 |
|---|---|---|
| 可变性 | 可变 | 不可变 |
| 符号 | [] |
() |
| 速度 | 较慢 | 较快 |
| 内存 | 较多 | 较少 |
| 用途 | 数据会改变 | 数据固定 |
| 作为字典key | 不可以 | 可以 |
point = (10, 20) # 坐标、RGB颜色、日期等固定数据
locations = {(0, 0): "原点", (10, 20): "点A"} # 作字典的键(列表不行)实用例子
坐标距离计算
import math
points = [(0, 0), (10, 20), (30, 40), (50, 60)]
def distance(p1, p2):
x1, y1 = p1
x2, y2 = p2
return math.sqrt((x2-x1)**2 + (y2-y1)**2)
for i in range(len(points) - 1): # 相邻点距离
print(f"{points[i]} 到 {points[i+1]} 距离:{distance(points[i], points[i+1]):.2f}")数据库查询结果
students = [("张三", 18, "male", 95), ("李四", 19, "female", 92)]
print("姓名\t年龄\t性别\t成绩")
print("-" * 30)
for name, age, gender, score in students: # 每行直接解包
gender_cn = "男" if gender == "male" else "女"
print(f"{name}\t{age}\t{gender_cn}\t{score}")颜色混合
RED, BLUE = (255, 0, 0), (0, 0, 255)
purple = tuple((a + b) // 2 for a, b in zip(RED, BLUE)) # (127, 0, 127)
DB_CONFIG = ("localhost", 5432, "mydb", "user", "password") # 常量配置不可改常见陷阱
- 单元素元组必须加逗号:
(1)是整数1,(1,)或1,才是元组。 - 元组含可变对象:元素为列表/字典时,其内容仍可修改(如
t[0].append(3))。 - 元组没有增删改方法:
append/sort/remove都不存在;排序请用sorted(t)返回新列表后转tuple()。
要点
- 元组不可变:不能改、增、删元素,只能重建;索引切片规则与列表一致。
- 单元素元组必须写
(1,),括号可省略,逗号才决定元组。 - 解包最实用:多变量赋值、交换变量、
*rest收集、函数返回多值。 - 元组的元素若是列表等可变对象,其内部仍可修改。
- 可作字典键、更快更省内存;
namedtuple支持按名字访问。数据会变用列表,固定用元组。
字典
字典(dict)用键值对(key-value)存储数据,通过 key 直接定位 value,底层是哈希表,查找为 O(1),与数据量无关。
student_list = ["张三", 18, "男", 95] # 列表:靠位置,不直观
student_dict = {"name": "张三", "age": 18, "gender": "男", "score": 95}
print(student_dict["name"]) # 字典:靠有意义的名字访问字典的特点
- 键值对存储:
{"name": "张三"}中"name"是 key,"张三"是 value。 - key 必须唯一:重复 key 后者覆盖前者,
{"name": "张三", "name": "李四"}得{'name': '李四'}。 - key 必须是不可变(可哈希)类型:字符串、数字、元组、布尔可以;列表、字典、集合不行(TypeError: unhashable type)。
- value 可以是任意类型:数字、字符串、列表、嵌套字典、布尔等。
- 有序(Python 3.7+):保持插入顺序;3.6 及以前不保证。
d = {"string": "字符串key", 123: "数字key", (1, 2): "元组key", True: "布尔key"}
# d = {[1, 2]: "x"} # TypeError:列表不可哈希创建字典
empty = {} # 空字典,或 dict()
person = {"name": "李四", "age": 25, "city": "北京"}
dict([("name", "王五"), ("age", 30)]) # 从键值对序列
dict(name="赵六", age=22, city="上海") # 关键字参数
dict(zip(["name", "age"], ["孙七", 28])) # 两个列表 zip 组合访问元素
student = {"name": "小明", "age": 18, "score": 95}
student["name"] # 小明;key 不存在抛 KeyError
student.get("gender") # None,不报错
student.get("gender", "未知") # 未知,可指定默认值优先用 get(),避免 KeyError。
修改与删除
student = {"name": "小明", "age": 18}
student["age"] = 19 # key 存在则修改
student["score"] = 95 # key 不存在则新增
del student["score"] # 按键删除,不存在抛 KeyError
age = student.pop("age") # 删除并返回 value,可给默认值 pop("age", 0)
item = student.popitem() # 删除并返回最后一对 (key, value)(3.7+ 保证顺序)
student.clear() # 清空字典操作
student = {"name": "小明", "age": 18, "score": 95}
"name" in student # True,判断 key 是否存在(不查 value)
list(student.keys()) # ['name', 'age', 'score']
list(student.values()) # ['小明', 18, 95]
list(student.items()) # [('name', '小明'), ('age', 18), ('score', 95)]合并字典
dict1, dict2 = {"a": 1, "b": 2}, {"b": 9, "c": 3}
dict1.update(dict2) # 原地合并,同名 key 被 dict2 覆盖:{'a': 1, 'b': 9, 'c': 3}
merged = {**dict1, **dict2} # 解包生成新字典(3.5+)
merged = dict1 | dict2 # 并集合并生成新字典(3.9+)复制字典
original = {"a": 1}
copy1 = original.copy() # 浅拷贝
copy2 = dict(original) # 浅拷贝
import copy
nested = {"a": {"b": 1}}
deep = copy.deepcopy(nested) # 深拷贝,嵌套对象也独立遍历字典
for key in student: # 默认遍历 key
print(key, student[key])
for value in student.values(): # 遍历 value
print(value)
for key, value in student.items(): # 同时遍历(最常用)
print(f"{key}: {value}")字典推导式
{x: x**2 for x in range(1, 6)} # {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
{x: x**2 for x in range(1, 11) if x % 2 == 0} # 带条件:{2: 4, 4: 16, ...}
{v: k for k, v in {"a": 1, "b": 2}.items()} # 反转字典:{1: 'a', 2: 'b'}嵌套字典
students = {
"student1": {"name": "张三", "scores": {"语文": 90, "数学": 95}},
"student2": {"name": "李四", "scores": {"语文": 88, "数学": 92}}
}
print(students["student1"]["scores"]["数学"]) # 95,逐层取键defaultdict 与 OrderedDict
from collections import defaultdict
counts = defaultdict(int) # 不存在的 key 自动取 int() = 0
counts["apple"] += 1 # 不会 KeyError
groups = defaultdict(list) # 默认值为列表
groups["fruits"].append("apple")from collections import OrderedDict # 3.7+ 普通字典已有序,仅 move_to_end 等仍有用
od = OrderedDict([("a", 1), ("b", 2)])
od.move_to_end("a") # OrderedDict([('b', 2), ('a', 1)])字典方法速查表
| 操作 | 写法 |
|---|---|
| 访问 | d[k](缺 key 报错);d.get(k[, 默认]);d.setdefault(k, 默认) |
| 增删改 | d[k] = v;d.update(other);del d[k] |
| 删除 | d.pop(k[, 默认]);d.popitem() 删最后一对;d.clear() |
| 视图 | d.keys() d.values() d.items()(动态视图,需 list() 转列表) |
| 判断 | k in d;len(d) |
| 复制 | d.copy() / dict(d) 浅拷贝;copy.deepcopy(d) 深拷贝 |
| 合并 | update() 原地;{**a, **b}(3.5+);`a |
实用例子
词频统计
words = "apple banana apple orange banana apple".split()
word_count = {}
for word in words:
word_count[word] = word_count.get(word, 0) + 1
# {'apple': 3, 'banana': 2, 'orange': 1}
from collections import Counter
Counter(words) # Counter({'apple': 3, 'banana': 2, 'orange': 1})学生成绩管理(嵌套字典)
students = {
"张三": {"语文": 90, "数学": 95, "英语": 88},
"李四": {"语文": 88, "数学": 92, "英语": 90},
}
for name, scores in students.items():
avg = sum(scores.values()) / len(scores)
print(f"{name}\t{avg:.1f}")
for subject in ["语文", "数学", "英语"]: # 每科平均
avg = sum(s[subject] for s in students.values()) / len(students)
print(f"{subject}平均分:{avg:.1f}")商品库存管理
inventory = {
"apple": {"price": 5.0, "quantity": 100},
"banana": {"price": 3.0, "quantity": 150},
}
def sell(product, quantity):
if product not in inventory:
return "商品不存在"
item = inventory[product]
if item["quantity"] < quantity:
return "库存不足"
item["quantity"] -= quantity
return f"售出{quantity}个{product},总价¥{item['price'] * quantity}"通讯录
contacts = {"张三": "138-1234-5678", "李四": "139-8765-4321"}
contacts["王五"] = "136-5555-6666" # 添加
phone = contacts.get(input("查找姓名:")) # 查找,找不到返回 None
for name, phone in contacts.items(): # 遍历
print(f"{name}: {phone}")常见陷阱
- key 必须是不可变类型:用列表、字典、集合作 key 抛
TypeError: unhashable type;改用元组。 - 遍历时修改字典:
for k in d: del d[k]抛RuntimeError;先固化键列表再删for k in list(d.keys())。 - 字典是引用:
d2 = d1后改d2会影响d1;要独立副本用d1.copy()(嵌套需deepcopy)。 d[k]缺 key 抛KeyError:不确定时用get()或先if k in d。- key 重复会静默覆盖:不会报错,后者生效。
要点
- 字典是 key-value 结构,哈希表实现,查找 O(1);Python 3.7+ 保持插入顺序。
- key 唯一且必须可哈希(不可变),value 任意;重复 key 后者覆盖前者。
- 访问用
get()更安全;setdefault、defaultdict适合作计数与分组。 - 遍历用
items();遍历中不要增删,需先list(d.keys())。 - 合并有
update()(原地)、{**a, **b}、a | b;复制默认浅拷贝,嵌套用deepcopy。 - 字典推导式
{k: v for ...}可快速构造与反转字典。
集合
无序、元素唯一的容器,基于哈希表实现,查找 O(1),主要用于去重和集合运算。
创建集合
numbers = {1, 2, 3, 4, 5}
fruits = {"apple", "banana", "orange"}
empty = set() # 空集合只能用 set()
# empty = {} # 错误!这是空字典
set([1, 2, 2, 3]) # {1, 2, 3},列表去重的标准做法
set("hello") # {'h', 'e', 'l', 'o'}
set((1, 2, 2, 3)) # {1, 2, 3}增删查
fruits = {"apple", "banana"}
fruits.add("orange") # 添加单个;已存在则无效果,不报错
fruits.update(["grape", "melon"]) # 添加多个(接受任意可迭代对象)
fruits.remove("banana") # 删除;不存在抛 KeyError
fruits.discard("grape") # 删除;不存在不报错
fruits.pop() # 随机删除并返回一个元素
fruits.clear() # 清空
"apple" in fruits # True,成员判断 O(1)
"grape" not in fruits # True集合运算
a = {1, 2, 3}
b = {3, 4, 5}
a | b # {1, 2, 3, 4, 5} 并集,等价于 a.union(b)
a & b # {3} 交集,等价于 a.intersection(b)
a - b # {1, 2} 差集(a有b没有),等价于 a.difference(b)
b - a # {4, 5}
a ^ b # {1, 2, 4, 5} 对称差集,等价于 a.symmetric_difference(b)带 _update 的方法会原地修改左侧集合:a |= b、a &= b、a -= b、a ^= b,以及 update/intersection_update/difference_update/symmetric_difference_update。
集合判断
a = {1, 2}
b = {1, 2, 3, 4}
a.issubset(b) # True,等价于 a <= b
a < b # True,真子集(包含且不相等)
b.issuperset(a) # True,等价于 b >= a
b > a # True,真超集
a.isdisjoint({4, 5}) # True,无共同元素集合推导式
squares = {x**2 for x in range(1, 6)} # {1, 4, 9, 16, 25}
even_squares = {x**2 for x in range(1, 11) if x % 2 == 0} # {4, 16, 36, 64, 100}frozenset
不可变集合,本身可哈希,因此能作为字典的 key 或集合的元素。
fs = frozenset([1, 2, 3])
# fs.add(4) # AttributeError
d = {frozenset([1, 2]): "a", frozenset([3, 4]): "b"}遍历
集合无序,遍历顺序不确定;需要稳定顺序时用 sorted()。
for fruit in fruits: # 顺序不确定
print(fruit)
for fruit in sorted(fruits): # 按排序结果遍历
print(fruit)实用例子
去重(含保持顺序)
numbers = [1, 2, 2, 3, 3, 3, 4, 4, 5]
list(set(numbers)) # [1, 2, 3, 4, 5],不保证原顺序
def unique_list(lst): # 保持原顺序去重
seen = set()
result = []
for item in lst:
if item not in seen:
seen.add(item)
result.append(item)
return result共同好友分析
friends_a = {"张三", "李四", "王五", "赵六"}
friends_b = {"李四", "王五", "孙七", "周八"}
friends_a & friends_b # {'李四', '王五'} 共同好友
friends_a - friends_b # {'张三', '赵六'} A 独有
friends_a | friends_b # 全部好友统计唯一单词
import string
text = "Python is great. Python is easy. I love Python."
words = [w.strip(string.punctuation) for w in text.lower().split()]
unique_words = set(words) # 唯一单词集合
len(unique_words) # 唯一单词数集合 vs 列表 vs 字典
| 特性 | 列表 | 集合 | 字典 |
|---|---|---|---|
| 顺序 | 有序 | 无序 | Python 3.7+ 有序 |
| 重复 | 允许 | 不允许 | key 不允许 |
| 可变 | 可变 | 可变 | 可变 |
| 索引 | 支持 | 不支持 | key 访问 |
| 查找速度 | O(n) | O(1) | O(1) |
| 适用场景 | 有序数据 | 去重、集合运算 | 键值对查找 |
常见陷阱
- 空集合:
{}是空字典,空集合必须写set()。 - 元素必须可哈希:
{[1, 2]}抛TypeError: unhashable type: 'list',改用元组{(1, 2)}。 - 无序性:
{3, 1, 2}的迭代/打印顺序不确定,依赖顺序就先sorted()。 - 需要顺序的去重结果:
list(set(x))会打乱顺序,改用「seen 集合 + 结果列表」。
要点
- 集合自动去重、无序、不支持索引,查找 O(1),远快于列表。
- 空集合用
set();{}是空字典。 - 元素必须是可哈希(不可变)类型;列表/集合/字典不能当元素,用元组或
frozenset代替。 - 四种运算各有运算符与方法两套写法:
|并、&交、-差、^对称差;加_update后缀为原地修改。 remove不存在会报KeyError,discard不会;pop随机删。frozenset是不可变集合,可作为字典 key。
条件判断
if / if-else / if-elif-else
条件后必须加冒号,代码块靠缩进界定;elif 可有任意多个,else 可选。
age = 18
if age >= 18:
print("你已成年")
print("可以投票")
print("程序继续") # 不在 if 内,总会执行score = 85
if score >= 90:
print("优秀")
elif score >= 80:
print("良好")
elif score >= 70:
print("中等")
elif score >= 60:
print("及格")
else:
print("不及格")执行顺序:从上到下依次判断,第一个为 True 的分支执行后立即跳出整个结构;全不满足才走 else。
条件表达式
# 比较:== != > < >= <=
5 == 5 # True
5 != 3 # True
# 逻辑:and(都真才真)、or(有一真即真)、not(取反)
age >= 18 and has_license
is_weekend or is_holiday
not is_raining
# 组合时用括号明确优先级
if age >= 18 and (has_ticket or has_money):
print("可以进场")
# 成员:in / not in(列表、字符串、集合、字典 key 均可)
if "apple" in ["apple", "banana"]:
pass
if "Hello" in "Hello World":
pass
# 身份:is / is not,比较是否为同一个对象
a = [1, 2, 3]; b = a; c = [1, 2, 3]
a is b # True(同一对象)
a is c # False(内容相同但对象不同)
a == c # True(值相等)
x is None # 判断 None 的标准写法is 只用于 None 和单例对象比较;判断值是否相等一律用 ==。
嵌套 if
if age >= 18:
if has_license:
print("有驾照" if has_car else "没车")
else:
print("没驾照")
else:
print("未成年")嵌套超过两层就该用卫语句或逻辑运算摊平。
三元表达式
status = "成年" if age >= 18 else "未成年"
max_num = a if a > b else b
result = "及格" if score >= 60 else "不及格"格式:值1 if 条件 else 值2——条件为真取值1,否则取值2。
真值测试
以下值在 if 中被视为 False:0、0.0、""、[]、()、{}、set()、None;其余非空/非零值均为 True。
if fruits: # 列表非空
print("有水果")
if name: # 字符串非空
print(f"你好,{name}")
if result is None: # 明确区分「空值」与「假值」时用 is None
print("没有结果")注意:if x 同时会漏掉 0 和 "",若它们是合法值,应写成 if x is not None。
match-case(Python 3.10+)
match command:
case "new":
print("创建新文件")
case "open":
print("打开文件")
case "save" | "saveas": # 用 | 匹配多个值
print("保存文件")
case _: # 默认分支
print("未知命令")实用例子
判断闰年
year = int(input("输入年份:"))
if (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0):
print(f"{year}是闰年")
else:
print(f"{year}不是闰年")BMI 分级
weight = float(input("体重(kg):"))
height = float(input("身高(m):"))
bmi = weight / (height ** 2)
print(f"你的BMI是:{bmi:.1f}")
if bmi < 18.5:
print("偏瘦")
elif bmi < 24:
print("正常")
elif bmi < 28:
print("偏胖")
else:
print("肥胖")三角形判断(嵌套 + 多条件组合)
a = float(input("边长a:"))
b = float(input("边长b:"))
c = float(input("边长c:"))
if a + b > c and a + c > b and b + c > a:
print("可以构成三角形")
if a == b == c:
print("等边三角形")
elif a == b or b == c or a == c:
print("等腰三角形")
else:
print("普通三角形")
else:
print("不能构成三角形")常见错误
- 忘记冒号:
if age >= 18后面缺:→SyntaxError。 - 缩进错误:分支代码未缩进或缩进不一致 →
IndentationError。 - 把
=当比较:if age = 18→SyntaxError,应为age == 18。 - 浮点数直接比较:
0.1 + 0.2 == 0.3为 False,应写abs((0.1 + 0.2) - 0.3) < 1e-9。
最佳实践
- 避免深层嵌套:用卫语句
if not cond: return提前返回,主体逻辑保持一层缩进。 - 用
in代替多个or:if x in [1, 2, 3]优于if x == 1 or x == 2 or x == 3。 - 提取重复代码:两分支共有的语句提到
if外面,分支内只留差异部分。
要点
- 结构:
if必选,elif可多个,else可选;条件后加冒号,代码块靠缩进。 - 条件从上到下依次判断,命中一个即跳出,条件顺序要从严到宽。
- 运算符:
== != > < >= <=、and or not、in / not in、is / is not(is只用于None)。 - 三元表达式:
值1 if 条件 else 值2。 - 假值:
0、0.0、""、[]、{}、None;其余为 True。 - 浮点比较用误差范围;深层嵌套用提前返回摊平。
for循环
for 变量 in 可迭代对象: 逐个取出元素,适合遍历序列或已知次数的循环。
遍历 range
for i in range(5): # 0, 1, 2, 3, 4(含头不含尾)
print(i)
for i in range(2, 6): # 2, 3, 4, 5
print(i)
for i in range(0, 10, 2): # 0, 2, 4, 6, 8(步长 2)
print(i)
for i in range(10, 0, -1): # 10, 9, ..., 1(步长为负则倒序)
print(i)遍历各种对象
fruits = ["apple", "banana", "orange"]
for fruit in fruits: # 直接取元素
print(fruit)
for i in range(len(fruits)): # 遍历索引
print(f"{i}: {fruits[i]}")
for i, fruit in enumerate(fruits): # 索引+元素(推荐)
print(f"{i}: {fruit}")
for i, fruit in enumerate(fruits, start=1): # 编号从 1 开始
print(f"{i}. {fruit}")
for char in "Python": # 字符串按字符遍历
print(char)
student = {"name": "小明", "age": 18, "score": 95}
for key in student: # 遍历 key
print(key)
for value in student.values(): # 遍历 value
print(value)
for key, value in student.items(): # 同时取 key 和 value(最常用)
print(f"{key}: {value}")
for num in {1, 2, 3, 4, 5}: # 集合遍历,顺序不确定
print(num)zip / reversed / sorted
names = ["张三", "李四", "王五"]
ages = [18, 19, 20]
scores = [90, 85, 92]
for name, age, score in zip(names, ages, scores): # 并行遍历,按最短的序列截断
print(f"{name}, {age}岁, {score}分")
zip(names, ages) # 惰性迭代器,需 list() 才实体化
dict(zip(keys, values)) # 两个列表合成字典
list(zip(*matrix)) # 矩阵转置
for fruit in reversed(fruits): # 反向遍历
print(fruit)
for i in reversed(range(5)): # 4, 3, 2, 1, 0
print(i)
for num in sorted(numbers): # 升序,不改原序列
print(num)
for num in sorted(numbers, reverse=True): # 降序
print(num)
for word in sorted(words, key=len): # 按长度排序
print(word)嵌套循环
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
for row in matrix: # 逐行遍历
for item in row:
print(item, end=" ")
print()
for i in range(len(matrix)): # 需要下标时
for j in range(len(matrix[i])):
print(f"[{i}][{j}] = {matrix[i][j]}")break
跳出所在的整个循环。
numbers = [1, 3, 5, 8, 9, 10]
for num in numbers:
if num % 2 == 0:
print(f"找到偶数:{num}")
break # 后面的元素不再检查
print(f"检查:{num}")
# 输出:检查:1 / 检查:3 / 检查:5 / 找到偶数:8嵌套循环中 break 只跳出当前这一层;要一次跳出多层需借助标志变量或把循环封装成函数用 return。
continue
跳过本次循环剩余语句,直接进入下一轮。
for i in range(10):
if i % 2 == 0:
continue
print(i) # 1, 3, 5, 7, 9for-else
循环未被 break 中断(正常走完)时执行 else 块,常用于「查找」场景。
num = 17
for i in range(2, num):
if num % i == 0:
print(f"{num}不是质数")
break
else:
print(f"{num}是质数")实用例子
九九乘法表(嵌套循环)
for i in range(1, 10):
for j in range(1, i + 1):
print(f"{j}×{i}={i*j}", end="\t")
print() # 一行结束换行成绩统计(遍历字典列表 + 条件累加)
students = [
{"name": "张三", "score": 85},
{"name": "李四", "score": 92},
{"name": "王五", "score": 78},
{"name": "赵六", "score": 95},
]
total = max_score = pass_count = 0
max_student = ""
for student in students:
name, score = student["name"], student["score"]
total += score
if score > max_score:
max_score, max_student = score, name
if score >= 60:
pass_count += 1
print(f"平均分:{total / len(students):.2f}")
print(f"最高分:{max_student} {max_score}分")
print(f"及格人数:{pass_count}/{len(students)}")猜数字(break + for-else)
import random
secret = random.randint(1, 100)
for i in range(5):
guess = int(input(f"第{i+1}次猜测:"))
if guess == secret:
print("恭喜你猜对了!")
break
elif guess < secret:
print("太小了")
else:
print("太大了")
else:
print(f"很遗憾,正确答案是{secret}")常见错误
- 遍历中修改列表:
for num in numbers: numbers.remove(num)会漏掉元素;改用列表推导式重建,或遍历副本numbers.copy()。 range参数误解:range(5, 1)是空循环,倒序必须给负步长range(5, 1, -1)。- 循环变量泄漏:
for i in ...结束后i仍存在(值为最后一个),嵌套循环用同名变量会互相覆盖。
避免在循环内重复计算不变量(如反复调用 len()),需要索引时用 enumerate;嵌套循环要注意规模带来的性能开销。
要点
range(a, b, s)含头不含尾;倒序需负步长。enumerate(可迭代对象, start=0)同时取索引与元素;zip()并行遍历多个序列,按最短截断。reversed()反向迭代,sorted(iterable, key=, reverse=)排序后迭代,二者都不改原对象。break跳出整个循环,continue跳过本轮;二者只作用于当前层循环。for...else:循环没被break才执行else,适合「找没找到」的判断。- 遍历时不要修改原列表,要改就遍历副本或用推导式重建。
while循环
条件为 True 时反复执行,适合循环次数未知的场景;已知次数优先用 for。
基本形式
count = 0
while count < 5:
print(f"第{count}次")
count += 1 # 必须有推进条件的语句,否则死循环先判断条件再执行;每次执行完重新判断;条件恒为 True 则无限循环。
while vs for
# while:次数未知、需要灵活控制
password = ""
while password != "123456":
password = input("请输入密码:")
balance = 100
while balance > 0:
cost = float(input("消费金额:"))
if cost > balance:
break # 中途退出
balance -= cost
# for:次数已知或遍历序列
for i in range(10):
print(i)
for fruit in ["apple", "banana"]:
print(fruit)break 与 continue
while True: # break 跳出循环
guess = int(input("猜一个数字:"))
if guess == secret:
print("猜对了!")
break
print("太小了" if guess < secret else "太大了")num = 0
while num < 10: # continue 跳过本轮剩余语句
num += 1
if num % 2 == 0:
continue # 注意:continue 前必须已推进 num,否则死循环
print(num) # 1, 3, 5, 7, 9while-else
条件变为 False 而正常结束(未被 break)时执行 else。
num = 17
i = 2
while i < num:
if num % i == 0:
print(f"{num}不是质数")
break
i += 1
else:
print(f"{num}是质数")无限循环
# 故意:服务器/交互式主循环,靠 break 退出
while True:
command = input("输入命令(quit退出):")
if command == "quit":
break
print(f"执行命令:{command}")
# 意外(bug)
count = 0
while count < 5:
print("循环")
# 忘记 count += 1,永远循环
while True:
print("无限循环")
# 没有 break停止失控的无限循环:按 Ctrl + C。
while True 常见模式
# 模式1:直到满足条件
while True:
if input("输入'yes'继续:") == "yes":
break
# 模式2:菜单循环
while True:
choice = input("选择:")
if choice == "quit":
break
# 处理选择
# 模式3:重试机制
max_retries, retry = 3, 0
while retry < max_retries:
try:
result = risky_operation()
break
except:
retry += 1
print(f"失败,重试{retry}/{max_retries}")嵌套 while
i = 1
while i <= 9:
j = 1
while j <= i:
print(f"{j}×{i}={i*j}", end="\t")
j += 1
print()
i += 1每层循环都要有自己独立的计数器与推进语句。
实战例子
登录系统(限次 + break)
username, password = "admin", "123456"
attempts = 3
while attempts > 0:
if input("用户名:") == username and input("密码:") == password:
print("登录成功!")
break
attempts -= 1
print(f"用户名或密码错误,还有{attempts}次机会" if attempts > 0 else "登录失败,账号已锁定")ATM 菜单(while True + 分支)
balance = 1000.0
while True:
print("\n=== ATM取款机 ===")
print("1. 查询余额\n2. 取款\n3. 存款\n4. 退出")
choice = input("请选择:")
if choice == "1":
print(f"当前余额:¥{balance:.2f}")
elif choice == "2":
amount = float(input("取款金额:"))
if amount > balance:
print("余额不足")
elif amount <= 0:
print("金额无效")
else:
balance -= amount
print(f"取款成功,余额:¥{balance:.2f}")
elif choice == "3":
amount = float(input("存款金额:"))
if amount <= 0:
print("金额无效")
else:
balance += amount
print(f"存款成功,余额:¥{balance:.2f}")
elif choice == "4":
print("感谢使用,再见!")
break
else:
print("无效选择")最大公约数(辗转相除法)
a = int(input("第一个数:"))
b = int(input("第二个数:"))
original_a, original_b = a, b
while b != 0:
a, b = b, a % b
print(f"{original_a}和{original_b}的最大公约数是{a}")常见错误
- 忘记更新条件:
while i < 5: print(i)缺i += 1→ 死循环。 - 条件方向写反:
while x > 0里写x += 1,x 越来越大 → 永远为真。 while True没有退出路径:必须至少有一个break(或return/异常)。continue位于计数器推进之前,导致计数器永不变化 → 死循环。
要点
- 语法:
while 条件:+ 缩进块,循环体内必须有推进/改变条件的语句。 while True+break是标准写法,用于菜单、交互循环、重试。break跳出循环,continue跳过本轮;else只在未被break中断时执行。- 已知次数用
for,未知次数用while;while更灵活但也更易写出死循环。 - 死循环用
Ctrl + C中断;排查时先检查计数器是否更新、条件方向是否正确。
推导式
用一行表达式从可迭代对象构造列表、字典、集合或生成器。
列表推导式
格式:[表达式 for 变量 in 序列],加过滤:[表达式 for 变量 in 序列 if 条件]。
squares = [x ** 2 for x in range(10)] # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
even_squares = [x ** 2 for x in range(10) if x % 2 == 0] # [0, 4, 16, 36, 64]
# 多个 if 等价于 and
[x for x in range(50) if x % 2 == 0 if x % 3 == 0]
[x for x in range(50) if x % 2 == 0 and x % 3 == 0]条件表达式(if-else)要放在 for 之前,用于给每个元素选值;而 if 过滤放在 for 之后。
# [表达式1 if 条件 else 表达式2 for 变量 in 序列]
numbers = [x if x % 2 == 0 else -x for x in range(10)]
# [0, -1, 2, -3, 4, -5, 6, -7, 8, -9]# 嵌套循环:多个 for 从左到右对应外层到内层
pairs = [(x, y) for x in [1, 2, 3] for y in ['a', 'b', 'c']]
# [(1,'a'), (1,'b'), (1,'c'), (2,'a'), ...]
# 字符串处理
words = ["hello", "world", "python"]
[w.upper() for w in words] # ['HELLO', 'WORLD', 'PYTHON']
[w[0] for w in words] # ['h', 'w', 'p']
[w for w in words if len(w) > 5] # ['python']
# 二维列表
matrix = [[i * 3 + j for j in range(3)] for i in range(3)] # 3x3 矩阵
transposed = [[row[i] for row in matrix] for i in range(3)] # 矩阵转置字典推导式
格式:{key表达式: value表达式 for 变量 in 序列}。
squares = {x: x ** 2 for x in range(6)} # {0:0, 1:1, 2:4, 3:9, 4:16, 5:25}
{word: len(word) for word in ["apple", "banana"]} # {'apple': 5, 'banana': 6}
{k: v for k, v in zip(keys, values)} # 两个列表合成字典
{x: x ** 2 for x in range(10) if x % 2 == 0} # 带条件过滤
{v: k for k, v in original.items()} # 反转字典(value 需可哈希且不重复)
{name: score for name, score in scores.items() if score >= 90} # 过滤
{city: t * 9/5 + 32 for city, t in celsius.items()} # 转换 value集合推导式
格式:{表达式 for 变量 in 序列},结果自动去重。
{x ** 2 for x in range(10)}
{char for char in "hello world" if char != ' '} # 唯一字符
{x + y for x in {1, 2, 3} for y in {4, 5}} # {5, 6, 7, 8}生成器表达式
格式:(表达式 for 变量 in 序列),返回惰性生成器,按需产出、几乎不占内存,但只能遍历一次。
squares_list = [x ** 2 for x in range(10)] # list,立即求值
squares_gen = (x ** 2 for x in range(10)) # generator,惰性
sum(x ** 2 for x in range(1000000)) # 直接传给函数时可再省一层括号只遍历一次或数据量巨大时用生成器;需要索引、多次访问或求长度时用列表。
实战例子
数据清洗与筛选
raw_data = [" apple ", "BANANA", " Orange", "grape "]
clean_data = [item.strip().lower() for item in raw_data]
# ['apple', 'banana', 'orange', 'grape']
import os
py_files = [f for f in os.listdir(".") if f.endswith(".py")]矩阵操作(嵌套推导式)
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
doubled = [[x * 2 for x in row] for row in matrix] # 每个元素乘 2
diagonal = [matrix[i][i] for i in range(len(matrix))] # [1, 5, 9]成绩统计(列表 + 字典推导式组合)
students = [
{"name": "张三", "score": 85},
{"name": "李四", "score": 92},
{"name": "王五", "score": 78},
{"name": "赵六", "score": 95},
]
scores = [s["score"] for s in students]
sum(scores) / len(scores) # 平均分
[s["name"] for s in students if s["score"] >= 60] # 及格名单
{s["name"]: s["score"] for s in students if s["score"] >= 90} # 优秀学生推导式 vs 传统循环
- 性能:推导式在解释器内部实现,通常比「空列表 +
append」略快。 - 可读性:简单映射/过滤用推导式;带多步处理、多重条件、异常处理的复杂逻辑仍用
for循环。
# 复杂逻辑拆成循环更清晰
result = []
for item in data:
if complex_condition(item):
processed = complex_processing(item)
if another_condition(processed):
result.append(processed)常见陷阱
- 过度嵌套:三层以上或带多重条件的推导式可读性骤降,拆成多步循环。
- 副作用:不要为了副作用写
[print(x) for x in range(10)],该场景用for循环(否则还会白白建一个列表)。 - 变量作用域:Python 3 中推导式有独立作用域,循环变量不会泄漏到外部(
print(x)会NameError)。 - 生成器一次性:遍历完即耗尽,再次迭代无输出,需要重复使用就转成列表。
要点
- 四种形式:
[ ]列表、{k: v}字典、{ }集合、( )生成器。 if过滤写在for之后(筛掉元素);if-else条件表达式写在for之前(给元素选值)。- 多个
for即嵌套循环,从左到右对应外层到内层,可生成笛卡尔积、二维矩阵。 - 字典推导式常用场景:
zip合并两列表、反转字典、按条件过滤、批量转换 value。 - 生成器表达式惰性求值、节省内存、只能遍历一次,适合
sum/max/any/all这类聚合场景。 - 简单变换用推导式,复杂逻辑和副作用操作用
for循环。
函数基础
函数是一段命名的可复用代码块。它的作用:
- 代码重用:同一段逻辑写一次、调多次(DRY)。
- 易于维护:改公式或样式只改函数内部一处。
- 提高可读性:
calculate_distance(3, 4)比(x**2 + y**2)**0.5表意清楚。 - 分解复杂问题:把大任务拆成
wash/cut/cook/serve,再由make_dinner()串联。
| 方面 | 不用函数 | 用函数 |
|---|---|---|
| 代码量 | 计算3个圆=15行 | 定义1次+调用3次=7行 |
| 维护性 | 改π要改3处 | 改π只改1处 |
| 可读性 | 看不出在做什么 | 函数名即语义 |
| 扩展性 | 加圆要复制代码 | 加圆只需一行调用 |
定义与调用
def function_name(parameters):
"""文档字符串(可选)"""
# 函数体
return result
def say_hello():
print("Hello!")
say_hello() # 输出:Hello!命名规范:小写+下划线,动词开头(calculate_area、get_user_input),避免 func、data 这类无意义名字。
参数
无参数 / 单个 / 多个
def greet(name):
print(f"Hello, {name}!")
def add(a, b):
return a + b
greet("小明") # 输出:Hello, 小明!
print(add(3, 5)) # 输出:8默认参数
def greet(name, greeting="你好"):
print(f"{greeting}, {name}!")
greet("小明") # 输出:你好, 小明!
greet("小红", "Hello") # 输出:Hello, 小红!默认参数必须放在无默认值参数之后:def func(a=1, b) 是 SyntaxError,应写成 def func(b, a=1)。
关键字参数
def describe_person(name, age, city):
print(f"{name}, {age}岁, 来自{city}")
describe_person("张三", 25, "北京") # 位置参数
describe_person(city="上海", name="李四", age=30) # 关键字参数,可换顺序
describe_person("王五", city="广州", age=28) # 混合时位置参数必须在前返回值
def square(x):
return x ** 2
def get_info():
return "张三", 25 # 多个值实际返回元组,可直接解包
def print_message(msg):
print(msg) # 无 return,默认返回 None
name, age = get_info()
print(print_message("Hello")) # 输出:None提前返回可简化分支:
def is_even(num):
return num % 2 == 0 # 等价于 if/else 返回 True/False文档字符串
def calculate_area(width, height):
"""
计算矩形面积
参数:
width: 宽度
height: 高度
返回:
矩形面积
"""
return width * height
print(calculate_area.__doc__)
help(calculate_area)函数调函数与递归
def is_even(n):
return n % 2 == 0
def count_even(numbers):
return sum(1 for num in numbers if is_even(num))
# 递归:函数调用自身
def factorial(n):
if n == 0 or n == 1:
return 1
return n * factorial(n - 1)
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(factorial(5)) # 输出:120
print([fibonacci(i) for i in range(10)]) # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]递归必须有终止条件:无终止条件会 RecursionError(默认递归深度约 1000)。重复子问题(如朴素 fibonacci)应改循环或加缓存。
def factorial_iter(n): # 等价的循环写法,无深度限制
result = 1
for i in range(2, n + 1):
result *= i
return result变量作用域
x = 10 # 全局变量
def my_function():
y = 10 # 局部变量,函数外不可见
print(x) # 可以读取全局变量
my_function()
# print(y) # NameError: y 不存在修改全局变量需要 global:
count = 0
def increment():
global count # 声明后赋值才会改全局,否则视作新建局部变量
count += 1
increment()
increment()
print(count) # 输出:2常见错误
- 忘记 return:函数没有 return 时返回 None,
result = add(3, 5)得到 None;需要结果就一定要 return。 - 缩进错误:函数体必须缩进,
def f():后正文不缩进报 IndentationError。 - 参数顺序错误:位置参数按顺序绑定,
divide(10, 2)是 5.0,divide(2, 10)是 0.2;易混时用关键字参数调用。
实战例子
BMI 计算器(多返回值 + 文档字符串)
def calculate_bmi(weight, height):
"""
计算BMI指数
参数:
weight: 体重(kg)
height: 身高(m)
返回:
BMI值和评价
"""
bmi = weight / (height ** 2)
if bmi < 18.5:
category = "偏瘦"
elif bmi < 24:
category = "正常"
elif bmi < 28:
category = "偏胖"
else:
category = "肥胖"
return bmi, category
bmi, category = calculate_bmi(70, 1.75)
print(f"BMI: {bmi:.1f}, 分类: {category}")密码强度验证(any + 校验分支)
def validate_password(password):
"""
验证密码强度
规则: 长度>=8、含大写字母、含小写字母、含数字
"""
if len(password) < 8:
return False, "密码太短"
if not any(c.isupper() for c in password):
return False, "缺少大写字母"
if not any(c.islower() for c in password):
return False, "缺少小写字母"
if not any(c.isdigit() for c in password):
return False, "缺少数字"
return True, "密码强度良好"
for pwd in ["abc", "Abc12345", "password", "Pass1234"]:
valid, message = validate_password(pwd)
print(f"{pwd}: {message}")格式化表格输出(组合用法)
def print_table(data, headers):
"""按列宽对齐打印表格"""
widths = [len(h) for h in headers]
for row in data:
for i, item in enumerate(row):
widths[i] = max(widths[i], len(str(item)))
header_line = " | ".join(h.ljust(widths[i]) for i, h in enumerate(headers))
print(header_line)
print("-" * len(header_line))
for row in data:
print(" | ".join(str(item).ljust(widths[i]) for i, item in enumerate(row)))
headers = ["姓名", "年龄", "城市"]
data = [["张三", 25, "北京"], ["李四", 30, "上海"], ["王五", 28, "广州"]]
print_table(data, headers)要点
def定义函数;参数支持位置参数、关键字参数、默认参数,默认参数必须写在后面。- 无
return返回 None;返回多个值实际是元组,可直接解包。 - 局部变量仅在函数内可见;修改全局变量用
global,嵌套函数修改外层变量用nonlocal。 - 递归要有终止条件,注意重复计算问题。
- 用文档字符串说明参数与返回值,可用
__doc__/help()查看。 - 一个函数只做一件事,函数名用小写+下划线并体现动作。
函数进阶
可变参数
*args 收集多余的位置参数为元组,**kwargs 收集多余的关键字参数为字典。
def sum_all(*args):
"""计算所有参数的和;args 是元组"""
return sum(args)
print(sum_all(1, 2, 3)) # 输出:6
print(sum_all()) # 输出:0
def func(a, b, *args, **kwargs): # kwargs 是字典
print(a, b, args, kwargs)
func(1, 2, 3, 4, 5, x=10, y=20) # 1 2 (3, 4, 5) {'x': 10, 'y': 20}参数顺序规则:普通参数 → *args → 默认参数 → **kwargs;def f(**kwargs, *args) 是 SyntaxError,形如 def f(a, b, *args, c=10, **kwargs)。
参数解包
def add(a, b, c):
return a + b + c
def greet(name, age, city):
print(f"{name}, {age}岁, 来自{city}")
add(*[1, 2, 3]) # 等价于 add(1, 2, 3),输出:6
add(*(4, 5, 6)) # 元组同样可解包,输出:15
person = {"name": "张三", "age": 25, "city": "北京"}
greet(**person) # 等价于 greet(name="张三", age=25, city="北京")仅限关键字与仅限位置参数
def func(a, b, *, c, d): # * 之后的参数必须用关键字传递
print(a, b, c, d)
func(1, 2, c=3, d=4) # 正确
# func(1, 2, 3, 4) # 错误:c 和 d 必须用关键字
def func2(a, b, /, c, d): # / 之前的参数只能用位置传递(Python 3.8+)
print(a, b, c, d)
func2(1, 2, c=3, d=4) # 正确
# func2(a=1, b=2, c=3, d=4) # 错误:a 和 b 只能用位置函数注解
类型提示仅作说明,运行时不强制检查。
def greet(name: str, age: int) -> str:
return f"{name}, {age}岁"
print(greet("张三", 25))
print(greet(123, "abc")) # 仍能运行,但不推荐lambda 表达式
单行的匿名函数,只能写一个表达式并自动返回结果;复杂逻辑用 def(完整语法与场景见第 16 章)。
闭包
内部函数引用了外部函数的变量,外部函数返回该内部函数,变量被保留下来。
def make_multiplier(n):
"""创建乘法器"""
def multiplier(x):
return x * n # 引用外部变量 n
return multiplier
times_2 = make_multiplier(2)
times_3 = make_multiplier(3)
print(times_2(5), times_3(5)) # 输出:10 15闭包内修改外层变量要用 nonlocal,否则 count += 1 会被当作新建局部变量而报 UnboundLocalError:
def make_counter():
count = 0
def counter():
nonlocal count
count += 1
return count
return counter
c1 = make_counter()
print(c1(), c1()) # 1 2(每次 make_counter 都是独立状态)装饰器
装饰器接收函数、返回新函数,用 @ 语法附加功能;@timer 等价于 slow_function = timer(slow_function),带参数的装饰器要多包一层工厂函数。
import time
from functools import wraps
def timer(func):
"""计时装饰器"""
@wraps(func) # 保留原函数的 __name__、__doc__ 等元信息
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
print(f"{func.__name__}用时: {time.time() - start:.3f}秒")
return result
return wrapper
@timer
def slow_function():
time.sleep(1)
return "完成"
slow_function() # slow_function用时: 1.001秒内置高阶函数
map 映射、filter 过滤、reduce 累积(需 from functools import reduce);map/filter 返回迭代器,只能消费一次。
numbers = [1, 2, 3, 4, 5]
print(list(map(lambda x: x ** 2, numbers))) # [1, 4, 9, 16, 25]
print(list(map(str.upper, ["hello", "world"]))) # 也可直接传函数名
print(list(filter(lambda x: x % 2 == 0, numbers))) # [2, 4],传 None 时按真假过滤
from functools import reduce
print(reduce(lambda x, y: x + y, numbers)) # 15
print(reduce(lambda x, y: x + y, [[1, 2], [3, 4]])) # 展平一层:[1, 2, 3, 4]key 决定排序依据,多条件用元组(数值取负实现降序):sorted(students, key=lambda x: (-x[1], x[0])) 表示分数降序、同分按姓名升序。
map / filter / reduce / sorted 的详细用法见第 16 章。
偏函数
functools.partial 预先固定部分参数,生成一个新函数。
from functools import partial
def power(base, exponent):
return base ** exponent
square = partial(power, exponent=2) # 固定 exponent=2,生成新函数
print(square(5)) # 25常见陷阱
- 默认参数是可变对象:
def add_item(item, lst=[])的列表在函数定义时创建一次,多次调用会累积元素;改用lst=None并在函数内if lst is None: lst = []。 - 闭包晚绑定:
[lambda: i for i in range(3)]中三个函数共享同一个i,调用时全是 2;用默认参数固定当前值lambda x=i: x,或改由函数工厂生成。 *args与**kwargs顺序:def f(**kwargs, *args)是 SyntaxError,必须是*args在前、**kwargs在后。- 漏写
@wraps:被装饰函数的__name__、__doc__会变成 wrapper 的,影响调试、日志与文档生成。
实战例子
缓存装饰器(闭包保存状态)
def memoize(func):
"""缓存函数结果"""
cache = {}
@wraps(func)
def wrapper(*args):
if args not in cache:
cache[args] = func(*args)
return cache[args]
return wrapper
@memoize
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(35)) # 无缓存会指数级递归,加缓存后秒出重试装饰器(带参数的装饰器)
def retry(max_attempts=3):
"""失败自动重试"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(max_attempts):
try:
return func(*args, **kwargs)
except Exception as e:
print(f"尝试 {attempt + 1} 失败: {e}")
if attempt == max_attempts - 1:
raise
return wrapper
return decorator
@retry(max_attempts=3)
def unstable_function():
import random
if random.random() < 0.7:
raise Exception("随机失败")
return "成功"要点
*args收位置参数为元组,**kwargs收关键字参数为字典;顺序为普通参数 →*args→ 默认参数 →**kwargs。*解包序列、**解包字典作为实参;*之后为仅限关键字参数,/之前为仅限位置参数。- 类型注解只是提示,不做运行时校验。
- 闭包保存外部变量状态,修改需
nonlocal;注意晚绑定问题(用默认参数固定)。 - 装饰器用
@语法,wrapper 用*args, **kwargs透传参数并加@wraps;带参数的装饰器要多包一层工厂函数。 - 默认参数绝不用可变对象;
partial可固定部分参数生成新函数。
Lambda和高阶函数
高阶函数是接收函数作为参数、或返回函数的函数;lambda 用于写一次性、单表达式的小函数,典型用途就是作为高阶函数的参数。
Lambda 基础语法
lambda 参数1, 参数2, ... : 表达式特点:匿名、只能写一个表达式、自动返回表达式结果、没有文档字符串。
square = lambda x: x ** 2
add = lambda x, y: x + y
greet = lambda: "Hello, World!"
print(square(5), add(10, 20), greet()) # 25 30 Hello, World!
# 带条件:用三元表达式
sign = lambda x: "正数" if x > 0 else ("负数" if x < 0 else "零")
print(sign(5), sign(-3), sign(0)) # 正数 负数 零Lambda 的使用场景
作为 key 参数传递
students = [("张三", 85), ("李四", 92), ("王五", 78)]
students.sort(key=lambda s: s[1]) # 按分数升序
students.sort(key=lambda s: s[1], reverse=True) # 按分数降序
words = ["apple", "pie", "banana", "cherry"]
words.sort(key=lambda w: len(w)) # 按长度:['pie', 'apple', 'banana', 'cherry']
points = [(1, 5), (3, 2), (5, 8), (2, 9)]
print(max(points, key=lambda p: p[1])) # (2, 9)字典排序
scores = {"张三": 85, "李四": 92, "王五": 78}
# 按 value 降序,重新构造有序字典
sorted_scores = dict(sorted(scores.items(), key=lambda x: x[1], reverse=True))
print(sorted_scores) # {'李四': 92, '张三': 85, '王五': 78}立即执行
print((lambda x, y: x * y)(5, 3)) # 15map() - 映射
对每个元素应用函数,返回迭代器(只能消费一次)。
numbers = [1, 2, 3, 4, 5]
print(list(map(lambda x: x ** 2, numbers))) # [1, 4, 9, 16, 25]
print(list(map(str.upper, ["hello", "world"]))) # 传函数名:['HELLO', 'WORLD']
# 多个序列并行(长度不齐时以最短的为准)
print(list(map(lambda x, y: x + y, [1, 2, 3], [10, 20, 30]))) # [11, 22, 33]
# 提取字段 / 格式化
students = [{"name": "张三"}, {"name": "李四"}]
print(list(map(lambda s: s["name"], students))) # ['张三', '李四']
print(list(map(lambda p: f"¥{p:.2f}", [10.5, 20.3]))) # ['¥10.50', '¥20.30']filter() - 过滤
保留使函数返回真的元素。
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(list(filter(lambda x: x % 2 == 0, numbers))) # [2, 4, 6, 8, 10]
print(list(filter(lambda x: x > 5, numbers))) # [6, 7, 8, 9, 10]
# 传 None 时直接按真假过滤,常用于剔除空串/None
print(list(filter(None, ["hello", "", "world"]))) # ['hello', 'world']
students = [{"name": "张三", "score": 85}, {"name": "李四", "score": 55}]
print(list(filter(lambda s: s["score"] >= 60, students))) # [{'name': '张三', 'score': 85}]reduce() - 归约
把序列两两累积成一个值,需 from functools import reduce。
from functools import reduce
numbers = [1, 2, 3, 4, 5]
print(reduce(lambda x, y: x + y, numbers)) # 15
print(reduce(lambda x, y: x * y, numbers)) # 120
print(reduce(lambda x, y: x if x > y else y, numbers)) # 5,求最大值
print(reduce(lambda x, y: x + y, ["Hello", " ", "World", "!"])) # Hello World!
print(reduce(lambda x, y: x * y, range(1, 6))) # 120,5 的阶乘
print(reduce(lambda x, y: x + y, [[1, 2], [3, 4]])) # [1, 2, 3, 4],展平一层组合使用
from functools import reduce
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# 链式:找偶数 → 平方 → 求和
total = reduce(lambda x, y: x + y,
map(lambda x: x ** 2,
filter(lambda x: x % 2 == 0, numbers)))
print(total) # 220
# 等价但更 Pythonic:生成器表达式 / 推导式
print(sum(x ** 2 for x in numbers if x % 2 == 0)) # 220嵌套过深时优先用推导式或拆成命名函数,可读性优先。
其他高阶函数
sorted()
sorted() 返回新列表,list.sort() 原地修改并返回 None;两者的 key 用法一致。
print(sorted([-5, 3, -8, 1, 9, -2], key=lambda x: abs(x))) # [1, -2, 3, -5, -8, 9]
students = [("张三", 85, 18), ("李四", 92, 19), ("王五", 85, 17)]
print(sorted(students, key=lambda s: (-s[1], s[2]))) # 分数降序,同分年龄升序zip()
names, ages, cities = ["张三", "李四"], [18, 19], ["北京", "上海"]
print(list(zip(names, ages, cities))) # [('张三', 18, '北京'), ('李四', 19, '上海')]
print(dict(zip(names, ages))) # {'张三': 18, '李四': 19}
zipped = [(1, 'a'), (2, 'b')]
numbers, letters = zip(*zipped) # 用 * 解包实现"解压"
print(numbers, letters) # (1, 2) ('a', 'b')all() 与 any()
print(all(x % 2 == 0 for x in [2, 4, 5])) # False:有一个不满足即为 False
print(any(x % 2 == 0 for x in [1, 3, 6])) # True:有一个满足即为 True
print(all([]), any([])) # True False:空序列的边界行为Lambda vs 普通函数
| 场景 | 选择 | 原因 |
|---|---|---|
| 单行简单表达式 | lambda | 就地定义,无需起名 |
| 作为 key / map / filter 的参数 | lambda | 用后即弃 |
| 多行、复杂逻辑 | def | lambda 只能写一个表达式 |
| 需要文档字符串 | def | lambda 无法写文档 |
| 需要多处复用 | def | 具名更易维护 |
常见陷阱
- Lambda 中的循环变量:
[lambda: i for i in range(3)]里的i是同一个变量,调用时全为 2;用默认参数固定当前值lambda x=i: x。 - Lambda 不能包含语句:
lambda x: print(x)是 SyntaxError(赋值、for、return 等语句都不行),需要语句就用def。 - 过度使用 lambda:嵌套的
filter(lambda ..., map(lambda ...))难以理解,拆成命名函数或改写为列表推导式。
实用例子
数据处理管道(map + filter 组合)
students = [
{"name": "张三", "score": 85, "age": 18},
{"name": "李四", "score": 92, "age": 19},
{"name": "王五", "score": 78, "age": 18},
{"name": "赵六", "score": 95, "age": 20},
]
# 取 18 岁以上且分数 >= 80 的学生姓名
result = list(map(lambda s: s["name"],
filter(lambda s: s["age"] > 18 and s["score"] >= 80, students)))
print(result) # ['李四', '赵六']
# 同样逻辑用推导式更清晰
result = [s["name"] for s in students if s["age"] > 18 and s["score"] >= 80]购物车折扣(lambda 规则 + 字段扩展)
cart = [
{"name": "T恤", "price": 99, "quantity": 2},
{"name": "裤子", "price": 199, "quantity": 1},
{"name": "鞋子", "price": 299, "quantity": 1},
]
discount_rate = lambda total: 0.9 if total >= 100 else 1.0 # 满100打9折
cart_with_subtotal = list(map(
lambda item: {**item, "subtotal": item["price"] * item["quantity"]}, cart))
total = sum(map(lambda item: item["subtotal"], cart_with_subtotal))
print(f"原价:¥{total},折后:¥{total * discount_rate(total):.2f}")CSV 数据转换(map + zip + dict)
csv_data = ["张三,18,北京", "李四,19,上海"]
students = list(map(lambda line: dict(zip(["name", "age", "city"], line.split(","))),
csv_data))
print(students)
# [{'name': '张三', 'age': '18', 'city': '北京'}, {'name': '李四', 'age': '19', 'city': '上海'}]
students = list(map(lambda s: {**s, "age": int(s["age"])}, students)) # 年龄转整数要点
- lambda 语法
lambda 参数: 表达式,只能一个表达式、自动返回、无文档,适合 key / map / filter 等一次性传参。 map映射、filter过滤、reduce累积;前两者返回迭代器,需要列表时用list(),reduce 需从functools导入。sorted/sort的key、max/min的key是最常见的 lambda 用武之地;多条件排序用元组 key(数值取负实现降序)。zip打包与zip(*x)解压,all/any配合生成器做整体判断。- 闭包晚绑定陷阱:循环里创建的 lambda 共享循环变量,用默认参数固定。
- 表达力与可读性冲突时选推导式或命名函数,不要为简短牺牲可读性。
模块和包
什么是模块
模块就是一个 .py 文件,里面可以放函数、类、变量。任何你写过的 .py 都能被别的程序 import。
导入模块时解释器做四件事:找到文件 → 执行文件里的全部代码 → 建立独立命名空间 → 返回模块对象。
# mymath.py
print("mymath 被导入了") # 导入时会执行一次
def add(a, b):
return a + b
PI = 3.14159# main.py
import mymath # 打印 "mymath 被导入了"
print(mymath.add(1, 2))
print(mymath.PI)| 要解决的问题 | 模块的作用 |
|---|---|
| 同一个函数在多个项目里复制粘贴 | 写一次,到处 import |
| 一个文件几千行 | 按功能域拆成多个模块 |
两人写了同名 send() |
用模块名做前缀,互不干扰 |
| 多人改同一文件冲突不断 | 各写各的模块,只约定接口 |
ecommerce/
├── orders.py # 订单
├── inventory.py # 库存
├── users.py # 用户
├── payments.py # 支付
└── shipping.py # 物流模块 vs 脚本:脚本直接运行完成任务(如 download_images.py),模块只定义函数供导入(如 image_utils.py)。用 if __name__ == "__main__" 可以让一个文件两者兼得。
导入模块的方式
import math # 1 整个模块
from math import pi, sqrt # 2 指定内容
from math import * # 3 全部(不推荐)
import numpy as np # 4 模块别名
from math import sqrt as sq # 4 函数别名
import os.path # 5 子模块
from os import pathimport 模块名
最推荐:访问要带 模块名. 前缀,来源一目了然,同名函数不会互相覆盖。
import math, cmath
print(math.sqrt(16)) # 4.0 实数平方根
print(cmath.sqrt(-1)) # 1j 复数平方根
import os, sys
print(os.path) # os 的 path
print(sys.path) # sys 的 path,不冲突适用:模块名短、要用多个函数、存在命名冲突风险。
from 模块名 import 名字
只导入需要的部分,省掉前缀。
from math import sqrt, pow, pi
print(sqrt(pow(pi, 2))) # 对比 math.sqrt(math.pow(math.pi, 2))
from datetime import datetime, timedelta # 也可导入类
now = datetime.now()
tomorrow = now + timedelta(days=1)风险是静默覆盖:from math import sqrt 后再 from numpy import sqrt,前者被顶掉且不报错。用别名区分:
from math import sqrt as math_sqrt
from numpy import sqrt as np_sqrtfrom 模块名 import *(慎用)
一次性导入所有不以下划线开头的名字,三个问题:
- 命名空间污染——不知道哪些名字被塞进当前作用域
- 命名冲突——
from math import *后from statistics import *,mean()被后者覆盖且无任何提示 - 可读性差——几个月后看不出
sqrt来自哪个模块
只在交互式环境(Jupyter)临时实验时可接受,正式代码一律显式导入。
别名 as
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from math import sqrt as square_root三个用途:名字太长(matplotlib.pyplot)、遵循社区约定、避开变量冲突(json = {...} 时改用 import json as JSON)。
| 模块 | 约定别名 |
|---|---|
| numpy | np |
| pandas | pd |
| matplotlib.pyplot | plt |
| seaborn | sns |
| tensorflow | tf |
| datetime.datetime | dt |
别名要有意义,不要 import numpy as n。
导入子模块
import os.path # 之后用 os.path.exists
from os import path # 之后用 path.exists
from urllib.request import urlopen
from collections.abc import Iterable控制导出:__all__
__all__ 决定 from 模块 import * 导出哪些名字;import * 本身就不会导出以下划线开头的名字。
# mymodule.py
__all__ = ["public_function"] # import * 只导出这一个
def public_function():
pass
def _private_function(): # 私有约定,不被 * 导出
pass导入方式怎么选
| 场景 | 写法 |
|---|---|
| 要用模块里的多个功能 | import math |
| 只用 1~3 个且高频使用 | from math import sqrt, pi |
| 模块名太长 | import matplotlib.pyplot as plt |
| 要区分不同来源的同名函数 | import math / cmath,或加别名 |
| 不确定要什么 | 不要 import * |
模块搜索路径(sys.path)
import 时按以下顺序查找:
- 当前目录(入口脚本所在目录)
- 环境变量
PYTHONPATH指定的目录 - 标准库目录
site-packages(第三方包安装位置)
import sys
for p in sys.path:
print(p)
sys.path.append("/my/custom/path") # 临时追加,只对当前进程有效
sys.path.insert(0, "/my/custom/path") # 插到最前,优先被找到自建模块 import 不到,绝大多数是路径问题:检查运行目录、用 sys.path 追加,或把项目做成可安装的包。
模块内省:__name__、dir()、help()
__name__ 与 if __name__ == “__main__”
直接运行文件时 __name__ 为 "__main__";被导入时 __name__ 是模块名。
# test.py
print(f"__name__ = {__name__}")python test.py # __name__ = __main__
python -c "import test" # __name__ = test把测试/演示代码关进这个块,别人导入时就不会被执行:
# calculator.py
def add(a, b):
return a + b
def multiply(a, b):
return a * b
if __name__ == "__main__":
print("测试加法:", add(3, 5))
print("测试乘法:", multiply(3, 5))python calculator.py 跑测试;import calculator 安静无声。它也是命令行工具的入口写法:
# image_resizer.py
import sys, os
from PIL import Image
def resize_image(src, dst, width, height):
img = Image.open(src)
img.resize((width, height)).save(dst)
return True
if __name__ == "__main__":
if len(sys.argv) != 5:
print("用法: python image_resizer.py <输入> <输出> <宽> <高>")
sys.exit(1)
src, dst, w, h = sys.argv[1], sys.argv[2], int(sys.argv[3]), int(sys.argv[4])
if not os.path.exists(src):
print(f"错误: 文件不存在: {src}")
sys.exit(1)
resize_image(src, dst, w, h)dir() 与 help()
import math
dir(math) # 模块中所有名字
dir() # 不传参:当前作用域的名字
help(math.sqrt) # 查看文档
math.sqrt.__doc__ # 直接取文档字符串
math.__file__ # 模块文件路径
math.__name__ # 模块名模块缓存与 reload
模块只在第一次 import 时执行,之后从 sys.modules 取缓存;运行中改了源码再 import 不会生效。
import importlib, mymodule
importlib.reload(mymodule) # 强制重跑模块代码
import sys
del sys.modules["mymodule"] # 或删掉缓存条目后重新 import
import mymodulereload 只重跑该模块本身:之前 from mymodule import X 出来的名字仍指向旧对象,已创建的实例也不会更新类。交互式调试才用,生产代码不要依赖。
创建自己的模块
一个 .py 文件写几个函数就是模块:
# greeting.py
def say_hello(name):
"""打招呼"""
return f"你好, {name}!"
def say_goodbye(name):
return f"再见, {name}!"# main.py
import greeting
print(greeting.say_hello("张三"))模块的推荐结构
"""模块文档字符串:用途、提供的功能、作者、版本"""
import os # 1 标准库
import requests # 2 第三方库
from mypkg import utils # 3 自己的模块
DEFAULT_ENCODING = "utf-8" # 4 模块级常量(全大写)
_cache = {} # 5 模块级变量
def _helper(): # 6 私有函数(_ 前缀,不对外)
pass
def public_func(): # 7 公开 API
pass
class MyClass: # 8 类定义
pass
def main(): # 9 入口
pass
if __name__ == "__main__":
main()函数文档字符串统一写得可被 help() 读取:
def divide(a, b):
"""
计算两个数的商
参数:
a: 被除数
b: 除数
返回:
a / b 的结果
异常:
ValueError: 除数为 0
例子:
>>> divide(10, 2)
5.0
"""
if b == 0:
raise ValueError("除数不能为0")
return a / b要点:写模块文档字符串、每个函数写文档、用有意义的名字、边界条件抛异常而不是 print、常量集中放顶部、_ 前缀表示私有。
一个完整示例:mymath
# mymath.py
"""我的数学工具模块:四则运算 + 圆相关计算"""
PI = 3.14159265359
E = 2.71828182846
def add(a, b):
return a + b
def subtract(a, b):
return a - b
def multiply(a, b):
return a * b
def divide(a, b):
if b == 0:
raise ValueError("除数不能为0")
return a / b
def circle_area(radius):
return PI * radius ** 2
def circle_circumference(radius):
return 2 * PI * radius
if __name__ == "__main__":
print(mymath_div := divide(10, 2))
print(f"r=5 面积: {circle_area(5):.2f}, 周长: {circle_circumference(5):.2f}")# main.py
import mymath
print(mymath.add(10, 5), mymath.PI)
try:
mymath.divide(10, 0)
except ValueError as e:
print(e) # 除数不能为0示例:file_utils 模块骨架
读文件失败时返回默认值而不是抛异常,是工具模块常见的“宽容”设计:
# file_utils.py
"""文件操作工具:安全读写、备份、按扩展名查找"""
import os
from datetime import datetime
def safe_read(filename, encoding="utf-8", default=""):
try:
with open(filename, "r", encoding=encoding) as f:
return f.read()
except FileNotFoundError:
print(f"文件不存在: {filename}")
return default
except UnicodeDecodeError: # 编码不对时退回 gbk 再试
try:
with open(filename, "r", encoding="gbk") as f:
return f.read()
except Exception:
return default
def safe_write(filename, content, encoding="utf-8"):
try:
with open(filename, "w", encoding=encoding) as f:
f.write(content)
return True
except Exception as e:
print(f"写入失败: {e}")
return False
def backup_file(filename):
if not os.path.exists(filename):
return None
name, ext = os.path.splitext(filename)
stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
backup_name = f"{name}_{stamp}{ext}"
safe_write(backup_name, safe_read(filename))
return backup_name
def list_files_by_extension(directory=".", extension=".txt"):
return [f for f in os.listdir(directory)
if os.path.isfile(os.path.join(directory, f)) and f.endswith(extension)]包(Package)
包就是含多个模块的文件夹,文件夹里必须有 __init__.py。
mypackage/
├── __init__.py
├── math_utils.py
├── string_utils.py
└── file_utils.py# mypackage/math_utils.py
def add(a, b):
return a + b
def multiply(a, b):
return a * b# mypackage/string_utils.py
def reverse(text):
return text[::-1]
def capitalize_words(text):
return text.title()__init__.py 的作用
- 标识这是一个包(Python 3.3+ 有命名空间包,可以没有它,但仍建议保留)
- 包的初始化代码:导入包时自动执行,可在这里聚合常用 API
- 控制
from package import *的行为(配合__all__)
# mypackage/__init__.py
from .math_utils import add, multiply
from .string_utils import reverse
__all__ = ["add", "multiply", "reverse"] # from mypackage import * 只导出这些
VERSION = "1.0.0"这样用户可以直接 from mypackage import add,而不必知道内部模块划分。
使用包的三种写法
from mypackage import math_utils # 1 导入子模块
print(math_utils.add(3, 5))
from mypackage.math_utils import add # 2 导入子模块里的具体函数
print(add(3, 5))
from mypackage import add # 3 走 __init__.py 暴露的 API
print(add(3, 5))绝对导入与相对导入
# mypackage/math_utils.py
from mypackage.string_utils import reverse # 绝对导入:从包根写全路径,推荐
from .string_utils import reverse # 相对导入:. 当前包,.. 上一级包
from . import helpers # 导入同级模块
from ..config import settings # 导入上一级的模块- 绝对导入:路径写全,清晰、不怕移动文件,推荐在应用代码中使用
- 相对导入:依赖当前包位置,改包名不用改导入;只能在包内部用,且不能在直接运行的脚本里用
python mypackage/module.py # 错误:相对导入在非包上下文中运行会报 ImportError
python -m mypackage.module # 正确:以模块方式运行子包
包里可以继续放包,形成层级:
mypackage/
├── __init__.py
├── utils/
│ ├── __init__.py
│ ├── text.py
│ └── number.py
└── core/
├── __init__.py
└── engine.pyfrom mypackage.utils import text
from mypackage.utils.text import slugify
from mypackage.core.engine import run较大的项目通常这样布局,源码、测试、文档、依赖分离:
project/
├── src/
│ ├── __init__.py
│ ├── core/
│ ├── utils/
│ └── models/
├── tests/
├── docs/
├── requirements.txt
└── README.md标准库常用模块速查
| 模块 | 一句话 | 常用入口 |
|---|---|---|
math |
数学函数 | sqrt pow ceil/floor/trunc factorial gcd sin/cos log/log10 pi e |
random |
随机数 | randint randrange random uniform choice sample choices shuffle |
datetime |
日期时间 | datetime.now() date.today() timedelta strftime/strptime |
os |
操作系统接口 | getcwd listdir makedirs environ;os.path.join/exists/isfile/isdir/abspath |
sys |
解释器相关 | argv path version exit() stdout/stderr modules |
json |
JSON 编解码 | dumps/loads(字符串)、dump/load(文件) |
re |
正则表达式 | search match findall sub compile |
collections |
扩展数据结构 | Counter defaultdict deque namedtuple |
itertools |
迭代工具 | chain product combinations groupby |
time |
时间戳与计时 | time() sleep() perf_counter() |
shutil |
高级文件操作 | copy copytree move rmtree disk_usage |
pathlib |
面向对象的路径 | Path("a/b.txt").read_text() / 拼接路径 |
argparse |
命令行参数解析 | ArgumentParser add_argument parse_args |
logging |
日志 | getLogger basicConfig debug/info/warning/error |
import math
math.ceil(3.2), math.floor(3.8), math.trunc(3.8) # 4, 3, 3
math.sqrt(16), math.pow(2, 3) # 4.0, 8.0
math.factorial(5), math.gcd(12, 18) # 120, 6
math.log(10), math.log10(100) # 2.302..., 2.0import random
random.randint(1, 10) # 1~10 随机整数
random.randrange(0, 10, 2) # 0,2,4,6,8 中随机一个
random.random() # 0~1 浮点
random.uniform(1.5, 5.5) # 区间浮点
random.choice(colors) # 随机选一个
random.sample(colors, 2) # 不重复抽 2 个
random.choices(colors, k=3) # 可重复抽 3 个
random.shuffle(numbers) # 原地打乱from datetime import datetime, date, timedelta
now = datetime.now()
today = date.today()
birthday = datetime(2000, 5, 20, 10, 30)
now.strftime("%Y-%m-%d %H:%M:%S") # 格式化为字符串
datetime.strptime("2024-01-15", "%Y-%m-%d") # 字符串解析为 datetime
tomorrow = now + timedelta(days=1)
(datetime.now() - birthday).days # 相隔天数import os
os.getcwd() # 当前工作目录
os.listdir(".") # 列出目录内容
os.makedirs("a/b", exist_ok=True) # 递归建目录
os.path.join("folder", "sub", "f.txt") # 跨平台拼接路径
os.path.exists("t.txt"), os.path.isfile("t.txt"), os.path.isdir("folder")
os.path.abspath("t.txt") # 绝对路径
os.environ.get("PATH") # 环境变量import sys
sys.argv # ['script.py', 'arg1', ...] 命令行参数
sys.path # 模块搜索路径
sys.version_info # 版本元组 (3, 11, 4, ...)
sys.exit(1) # 退出程序
sys.modules # 已加载模块缓存第三方包与 pip
pip install requests # 安装
pip install numpy pandas matplotlib # 一次装多个
pip install requests==2.28.0 # 装指定版本
pip install "requests>=2.25,<3" # 版本区间
pip install --upgrade requests # 升级
pip uninstall requests # 卸载
pip list # 已安装的包
pip show requests # 包的详细信息
pip search xxx # 搜索(官方源已停用,改去 pypi.org 搜)
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple numpy # 国内镜像加速requirements.txt 管理依赖:
pip freeze > requirements.txt # 导出当前环境依赖(含精确版本)
pip install -r requirements.txt # 按文件还原依赖# requirements.txt
requests==2.28.0
pandas==1.5.0
numpy==1.23.0虚拟环境 venv
不同项目可能需要同一个包的不同版本(项目 A 要 Django 3.0、项目 B 要 Django 4.0),虚拟环境给每个项目一套独立的 site-packages。
python -m venv myenv # 创建
# 激活
myenv\Scripts\activate # Windows (cmd/PowerShell)
source myenv/bin/activate # macOS / Linux
deactivate # 退出激活后 python / pip 都指向虚拟环境,pip install 不会污染全局。常用流程:建 venv → 激活 → pip install -r requirements.txt → 开发 → pip freeze > requirements.txt。虚拟环境目录(如 myenv/)不要提交到 Git。
常见错误与陷阱
| 陷阱 | 原因 | 解决 |
|---|---|---|
循环导入:a 导入 b、b 又导入 a,报 ImportError: cannot import name |
两个模块互相依赖,导入时对方还没定义完 | 把公共部分抽到第三个模块;或延迟到函数内部 import;或改成 import module 而非 from module import name |
| 改了模块源码,重跑程序还是旧代码 | 模块被缓存在 sys.modules,只首次导入时执行 |
importlib.reload(mymodule) 或重启解释器 |
自建模块 ModuleNotFoundError |
模块所在目录不在 sys.path 里 |
在项目根目录运行;或 sys.path.append(目录);或做成可安装的包 |
相对导入报 ImportError: attempted relative import with no known parent package |
把包内模块当脚本直接运行 | 用 python -m 包名.模块名 运行,不要 python 包/模块.py |
没有 __init__.py 也能导入,但行为怪 |
Python 3.3+ 的命名空间包规则不同 | 显式创建 __init__.py,明确包边界并控制导出 |
from math import * 后某个函数结果不对 |
后导入的模块覆盖了同名函数,且不报错 | 不用 import *;用 import module 或别名 |
模块名和标准库重名(如 random.py、json.py) |
当前目录优先于标准库,遮住了真模块 | 改名,且不要起 sys.py、os.py 这类名字 |
| 导入模块就打印一堆测试输出 | 模块顶层写了测试/执行代码 | 放进 if __name__ == "__main__": 块 |
| 模块名用了大写、连字符或数字开头 | 不符合标识符规范,无法导入 | 用小写+下划线:math_utils.py |
importlib.reload 后旧对象仍未更新 |
已导入的名字/实例仍指向旧模块 | 重新 from module import name,或重启 |
要点
- 模块是一个
.py文件,包是带__init__.py的文件夹;导入即执行文件、建命名空间、存进sys.modules。 - 优先
import 模块或from 模块 import 名字,几乎不要import *;需要区分同名函数时用as别名。 __all__控制from 包/模块 import *的导出内容,_前缀的名字默认不导出。sys.path决定去哪找模块(当前目录 → PYTHONPATH → 标准库 → site-packages),找不到模块先查路径。- 直接运行时
__name__ == "__main__",被导入时是模块名;测试代码和命令行入口都放进if __name__ == "__main__":。 - 包内用绝对导入,跨包相对导入(
./..)只能用于包内且需python -m运行。 - 第三方包用
pip+requirements.txt管理,每个项目配一个venv虚拟环境隔离依赖。 - 循环导入、模块缓存(改代码不生效)、模块名与标准库重名是最高频的三个坑。
文件读写
程序运行时数据都在内存里,程序一关就没了;文件操作把数据写到硬盘实现持久化。基本流程:打开 → 读写 → 关闭。
打开文件
基本语法与路径
open(filename, mode, encoding):mode 默认 'r',文本模式下 encoding 一律显式写 "utf-8"。
open("data.txt", "r") # 相对路径,基准是 os.getcwd()
open("documents/data.txt", "r") # 相对路径
open("../data.txt", "r") # 上一级目录
open(r"C:\Users\Me\data.txt") # 绝对路径;用 r"" 避免 \ 被当转义符
open("/home/me/data.txt") # Linux/Mac模式 r / w / a / x / b / t / +
# 'r' 只读(默认):文件不存在抛 FileNotFoundError
with open("book.txt", "r", encoding="utf-8") as f:
content = f.read() # f.write(...) 会报错
# 'w' 只写:不存在则创建,存在则清空原内容(危险,先确认不再需要原内容)
with open("output.txt", "w", encoding="utf-8") as f:
f.write("新内容")
# 'a' 追加:写到文件末尾,保留原内容,适合日志
with open("app.log", "a", encoding="utf-8") as f:
f.write("[2024-01-15 10:30:00] 用户登录\n")
# 'x' 独占创建:已存在则抛 FileExistsError,防止误覆盖
try:
with open("report.txt", "x", encoding="utf-8") as f:
f.write("报告内容")
except FileExistsError:
print("文件已存在,不能覆盖")
# 'r+' / 'w+' / 'a+':可读可写。r+ 要求文件存在,w+ 先清空,a+ 追加
with open("data.txt", "w+", encoding="utf-8") as f:
f.write("新内容")
f.seek(0) # 写完指针在末尾,必须回到开头才能读到
print(f.read())
# 加 'b' 为二进制模式:图片/音频/视频/压缩包/可执行文件必须用
with open("photo.jpg", "rb") as src, open("photo_copy.jpg", "wb") as dst:
dst.write(src.read()) # 读写的是 bytes:b'...'- 文本模式返回
str,二进制模式返回bytes;'t'是默认的文本模式,一般不写 +模式用得少,读写分开更清晰安全- 常见场景:读配置
'r'、写日志'a'、导出数据'w'、复制图片'rb'/'wb'、安全新建'x'
编码 encoding
编码是“文字 ↔ 二进制”的映射规则,用错就是乱码(UTF-8 中文按 GBK 读会变 浣犲ソ)。
open("f.txt", encoding="utf-8") # 国际通用,支持全部语言,首选
open("f.txt", encoding="gbk") # Windows 中文环境的老文件
open("f.txt", encoding="ascii") # 仅英文# 不知道文件编码时依次回退尝试
def read_with_fallback(filename):
for encoding in ("utf-8", "gbk", "latin-1"):
try:
with open(filename, "r", encoding=encoding) as f:
return f.read()
except UnicodeDecodeError:
continue
raise UnicodeDecodeError("unknown", b"", 0, 1, "无法解码文件")with 上下文管理器
不用 with 时,一旦中间抛异常 close() 就执行不到(文件句柄泄漏、改动未落盘)。with 进入时调 __enter__(),离开时无论是否异常都调 __exit__() 关闭文件。
# 等价于 try/finally 版,但更简洁
with open("data.txt", "r", encoding="utf-8") as f:
content = f.read()
# 离开 with 块自动关闭
# 同时打开多个文件
with open("input.txt", "r", encoding="utf-8") as fin, \
open("output.txt", "w", encoding="utf-8") as fout:
fout.write(fin.read().upper())文件模式速查表
| 模式 | 描述 | 文件存在 | 文件不存在 | 读 | 写 | 清空原内容 |
|---|---|---|---|---|---|---|
'r' |
只读 | 打开 | 报错 FileNotFoundError | ✓ | ||
'w' |
只写 | 清空后打开 | 创建 | ✓ | ✓ | |
'a' |
追加 | 打开,写指针在末尾 | 创建 | ✓ | ||
'x' |
独占创建 | 报错 FileExistsError | 创建 | ✓ | ||
'r+' |
读写 | 打开 | 报错 | ✓ | ✓ | |
'w+' |
读写 | 清空后打开 | 创建 | ✓ | ✓ | ✓ |
'a+' |
读写追加 | 打开 | 创建 | ✓ | ✓ |
加 'b' 即二进制模式:'rb'、'wb'、'ab'、'rb+'、'wb+'、'ab+'。
读取文件
read() — 一次读完
with open("config.txt", "r", encoding="utf-8") as f:
content = f.read() # str,含全部内容(适合几 KB~几 MB 的小文件)
with open("file.txt", "r", encoding="utf-8") as f:
first_10 = f.read(10) # 按字符数读,指针随之后移
next_5 = f.read(5)
rest = f.read() # 剩余全部大文件不要用 read(),会一次性占满内存。
readline() — 一次读一行
with open("file.txt", "r", encoding="utf-8") as f:
while True:
line = f.readline()
if not line: # 读到末尾返回空字符串 ""
break
print(line.rstrip("\n"))返回值带行尾换行符 \n,用 strip() 或 rstrip("\n") 去掉;读到末尾返回空字符串 ""。适合需逐行决策的场景(找到目标行就停)。
readlines() — 全部行读成列表
with open("file.txt", "r", encoding="utf-8") as f:
lines = f.readlines() # ['第一行\n', '第二行\n', '第三行\n']
clean = [line.strip() for line in lines]
print(lines[0], lines[-1]) # 可用索引随机访问同样把整个文件载入内存;好处是可反复遍历、按索引取行、跳过表头(lines[1:])。
迭代文件对象(推荐)
with open("file.txt", "r", encoding="utf-8") as f:
for line in f: # 惰性逐行读,内存友好,无需调方法
print(line.strip())
# 带行号 / 跳过空行注释 / 过滤转换
with open("app.log", "r", encoding="utf-8") as f:
for line_num, line in enumerate(f, start=1):
line = line.strip()
if not line or line.startswith("#"):
continue
print(line_num, line)
with open("numbers.txt", "r", encoding="utf-8") as f:
numbers = [int(line) for line in f if line.strip().isdigit()]四种读取方式对比
| 方式 | 内存占用 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|---|
read() |
高(全部加载) | 小文件、需整体字符串处理 | 简单直接 | 大文件爆内存 |
readline() |
低(逐行) | 需逐行决策 | 内存友好 | 代码繁琐 |
readlines() |
高(全部加载) | 需多次访问/按索引取行 | 可索引、可知行数 | 大文件爆内存,需去 \n |
| 迭代文件对象 | 低(逐行) | 大多数情况(推荐) | 简洁高效 | 无 |
写入文件
name, score = "张三", 85
with open("result.txt", "w", encoding="utf-8") as f:
f.write(f"姓名:{name}\n") # write() 不会自动加换行,必须自己写 \n
f.write("分数:{}\n".format(score))
f.write(str(score) + "\n") # 数字等非字符串要先 str()
with open("result.txt", "a", encoding="utf-8") as f:
f.write("追加一行\n") # 'a' 追加,不清空原内容
lines = ["第一行\n", "第二行\n", "第三行\n"]
with open("output.txt", "w", encoding="utf-8") as f:
f.writelines(lines) # 每个元素必须自带 \n'w'覆盖(原内容丢失),'a'追加(原内容保留)write()返回写入的字符数;writelines()只是批量write(),不补换行
文件定位:seek() 与 tell()
with open("file.txt", "r", encoding="utf-8") as f:
print(f.tell()) # 0,当前指针位置(字节偏移)
f.read(10)
print(f.tell()) # 10
f.seek(0) # 回到开头,之后可重新读一遍seek(offset, whence):whence 0=文件开头(默认)、1=当前位置、2=文件末尾。文本模式下 whence 只能用 0(或 tell() 得到的偏移)。
文件路径处理
os.path 常用操作
import os
os.getcwd() # 当前工作目录
os.path.join("data", "files", "a.txt") # 跨平台拼接:data/files/a.txt(Win: data\files\a.txt)
os.path.exists("file.txt") # 是否存在
os.path.isfile("test.txt") # 是否文件
os.path.isdir("data") # 是否目录
os.path.getsize("file.txt") # 字节大小
os.path.dirname("/home/u/doc/a.txt") # /home/u/doc
os.path.basename("/home/u/doc/a.txt") # a.txt
os.path.splitext("a.tar.gz") # ('a.tar', '.gz')
os.path.abspath("a.txt") # 转绝对路径
os.mkdir("data") # 建单级目录,父目录不存在则报错
os.makedirs("data/files/backup", exist_ok=True) # 建多级,exist_ok=True 已存在不报错
os.listdir("data") # 列出目录下条目名
os.remove("a.txt") # 删除文件
os.rmdir("data") # 删除空目录pathlib 常用操作(Python 3.4+,推荐)
from pathlib import Path
file = Path("data") / "file.txt" # 用 / 拼接路径
file.exists() # 是否存在
file.is_file() / file.is_dir()
file.name, file.stem, file.suffix # 文件名 / 去后缀名 / 后缀
file.parent # 父目录
file.resolve() # 绝对路径
file.stat().st_size # 字节大小
file.read_text(encoding="utf-8") # 一步读文本
file.write_text("内容", encoding="utf-8")
file.read_bytes() / file.write_bytes(b"...")
Path("data").mkdir(parents=True, exist_ok=True) # 建目录(含父级)
file.unlink(missing_ok=True) # 删除文件
file.rename("new.txt")
for p in Path("data").iterdir(): pass # 遍历一层
for p in Path("data").glob("*.txt"): pass # 按模式匹配
for p in Path("data").rglob("*.py"): pass # 递归匹配删除非空目录用 shutil.rmtree("data");复制/移动文件用 shutil.copy(src, dst)、shutil.move()。
os.path / pathlib 速查表
| 需求 | os.path | pathlib |
|---|---|---|
| 拼接 | os.path.join(a, b) |
Path(a) / b |
| 当前目录 | os.getcwd() |
Path.cwd() |
| 绝对路径 | os.path.abspath(p) |
Path(p).resolve() |
| 判断存在/文件/目录 | os.path.exists/isfile/isdir |
.exists()/.is_file()/.is_dir() |
| 取目录名/文件名 | os.path.dirname/basename |
.parent/.name |
| 后缀 | os.path.splitext(p)[1] |
.suffix |
| 大小 | os.path.getsize(p) |
.stat().st_size |
| 建目录 | os.makedirs(p, exist_ok=True) |
Path(p).mkdir(parents=True, exist_ok=True) |
| 遍历 | os.listdir(d) |
.iterdir() / .glob() / .rglob() |
| 删除文件 | os.remove(p) |
.unlink() |
| 删除目录 | os.rmdir(p)(须为空) |
.rmdir()(须为空)/ shutil.rmtree(p) |
| 读文本 | open(p).read() |
.read_text(encoding="utf-8") |
二进制文件
凡是非纯文本(图片、音频、视频、zip、exe)一律 'rb'/'wb',且不带 encoding。
with open("image.png", "rb") as f:
data = f.read() # <class 'bytes'>,len(data) 为字节数
# 复制图片/视频等:分块读写,避免大文件占内存
with open("source.png", "rb") as src, open("copy.png", "wb") as dst:
while chunk := src.read(1024 * 1024): # 每次 1MB
dst.write(chunk)异常处理
常见文件异常
try:
with open("nonexistent.txt", "r", encoding="utf-8") as f:
content = f.read()
except FileNotFoundError: # 'r' 模式下文件不存在
print("文件不存在")
except PermissionError: # 无读写权限(如写 /root)
print("没有权限")
except UnicodeDecodeError: # 编码不匹配,可回退 gbk 再试
print("编码错误")
except IsADirectoryError: # 把目录当文件打开
print("这是目录")
except OSError as e: # FileNotFoundError 等的父类,兜底
print(f"I/O 出错:{e}")安全读写封装
def safe_read_file(filename, default=""):
try:
with open(filename, "r", encoding="utf-8") as f:
return f.read()
except FileNotFoundError:
print(f"文件不存在:{filename}")
return default
except OSError as e:
print(f"读取失败:{e}")
return defaultFileNotFoundError、PermissionError、IsADirectoryError 都继承自 OSError(IOError 是其别名)。
实用例子
例子1:解析 key=value 配置文件
def read_config(filename):
"""读取 key=value 配置,跳过空行与 # 注释"""
config = {}
try:
with open(filename, "r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if not line or line.startswith("#"):
continue
if "=" in line:
key, value = line.split("=", 1) # 只切一次,值里允许有 =
config[key.strip()] = value.strip()
except FileNotFoundError:
print(f"配置文件不存在:{filename}")
return config
# config.txt: host=localhost / port=8080 / # 注释 / debug=true例子2:日志记录器
from datetime import datetime
class Logger:
def __init__(self, filename="app.log"):
self.filename = filename
def _write(self, level, message):
ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
with open(self.filename, "a", encoding="utf-8") as f: # 'a' 保留历史日志
f.write(f"[{ts}] [{level}] {message}\n")
def info(self, msg): self._write("INFO", msg)
def warning(self, msg): self._write("WARNING", msg)
def error(self, msg): self._write("ERROR", msg)
logger = Logger()
logger.info("程序启动")
logger.error("数据库连接失败")例子3:分块复制大文件 + 目录内关键词搜索
import os
def copy_file(source, destination):
"""分块复制,适合 GB 级文件;返回是否成功"""
if not os.path.exists(source):
print(f"源文件不存在:{source}")
return False
try:
os.makedirs(os.path.dirname(destination), exist_ok=True)
with open(source, "rb") as src, open(destination, "wb") as dst:
while chunk := src.read(1024 * 1024):
dst.write(chunk)
return True
except OSError as e:
print(f"复制失败:{e}")
return False
def search_in_directory(directory, keyword, extension=".txt"):
"""遍历目录(非递归)搜索关键词,返回 [(文件名, 行号, 行内容)]"""
results = []
for filename in os.listdir(directory):
if not filename.endswith(extension):
continue
with open(os.path.join(directory, filename), "r", encoding="utf-8") as f:
for line_num, line in enumerate(f, 1):
if keyword.lower() in line.lower():
results.append((filename, line_num, line.strip()))
return results最佳实践
with open("file.txt", "r", encoding="utf-8") as f: # 始终用 with
for line in f: # 大文件逐行迭代,不用 read()
line = line.strip()
if not line or line.startswith("#"): # 跳过空行与注释
continue
process(line)- 显式写
encoding="utf-8";路径拼接用os.path.join()或pathlib,别手写分隔符 - 写入前确认模式:
'w'会清空原文件;非文本文件用'b'且不带encoding - 用
try/except FileNotFoundError等处理文件异常
要点
- 文件操作三步:打开 → 读写 → 关闭;始终用
with,异常时也会自动关闭 - 模式:
'r'读(不存在报错)、'w'写(清空原内容)、'a'追加、'x'独占创建(已存在报错)、'b'二进制、'+'读写 - 读取首选迭代文件对象(省内存);
read()/readlines()只用于小文件或需要整体、按索引访问时 write()不自动加\n,writelines()要求元素自带\n;写完再读需先seek(0),tell()查当前位置- 路径优先用
pathlib(/拼接、glob/rglob遍历、mkdir(parents=True, exist_ok=True)建目录),老代码常见os.path.join/exists/makedirs - 异常:
FileNotFoundError、PermissionError、UnicodeDecodeError、IsADirectoryError均属OSError;显式指定encoding="utf-8",非文本文件用二进制模式
异常处理
异常(Exception)是程序运行时发生的错误,不处理就会崩溃并抛出 traceback。它与语法错误不同:语法错误(SyntaxError)是代码本身写错,解释器拒绝运行,必须改代码;异常的语法正确,只是运行时条件不满足,可以用 try/except 捕获并让程序继续。
result = 10 / 0 # ZeroDivisionError: division by zero,后续代码不会执行
try:
result = 10 / 0
except ZeroDivisionError:
result = 0
print("程序继续运行") # 会执行不处理异常的代价:程序直接退出、用户看到一堆堆栈、还可能留下不一致状态(例如钱已扣但订单未处理)。异常处理带来四点收益:错误可控、提示友好、出错可回滚、便于记录日志。
try/except 基本语法与执行顺序
try:
risky_code() # 1. 先执行 try 块
except SomeError: # 2. 抛出的异常类型匹配才进入
handle_error()- try 块中没有异常 → 跳过所有 except,继续执行整个结构之后的代码。
- try 块中一旦抛异常 → 立即中断剩余语句,从上到下依次匹配 except;第一个匹配的分之执行。
- 没有任何 except 匹配 → 异常继续向外层调用栈传播,最终未被捕获则程序终止。
- 多个 except 时,子类必须写在父类前面,否则父类先匹配,子类的分支永远不执行。
def safe_divide(a, b):
try:
return a / b
except ZeroDivisionError:
return None
print(safe_divide(10, 0)) # None异常类的层次
BaseException → Exception →(ValueError、TypeError、KeyError、IndexError、AttributeError、OSError …)。
SystemExit、KeyboardInterrupt、GeneratorExit 直接继承自 BaseException,不属于 Exception,这也是 except Exception 不会拦住 Ctrl+C 的原因。
捕获多个异常
# 写法1:多个 except 块,分别处理
try:
number = int(input("输入数字: "))
print(10 / number)
except ValueError:
print("输入的不是数字")
except ZeroDivisionError:
print("除数不能为0")
# 写法2:一个 except 捕获多种异常(元组)
try:
operation()
except (ValueError, TypeError, KeyError) as e:
print(type(e).__name__, e)异常对象 as e
as e 把异常实例绑定到变量,常用属性与方法:
| 属性/写法 | 含义 |
|---|---|
str(e) / f"{e}" |
异常消息 |
type(e).__name__ |
异常类名 |
e.args |
构造异常时传入的参数元组 |
e.errno / e.filename(OSError 系) |
错误号、相关文件名 |
try:
int("二十岁")
except ValueError as e:
print(f"{type(e).__name__}: {e}") # ValueError: invalid literal for int()...try/except/else/finally
完整结构及执行顺序:
try:
risky_operation() # ① 可能出错的代码
except SomeError as e: # ② 异常匹配时执行
handle_error()
else: # ③ try 未抛异常时执行
success_handling()
finally: # ④ 无论如何都执行(清理资源)
cleanup()else用于放“成功才做”的逻辑,好处是这些代码抛出的异常不会被自己的 except 误吞;else中定义的变量在 try 中可见。finally一定执行,即便 try 中有return、有异常、或被break/continue跳出,常用于关闭文件、释放锁、断开连接。- 不要在 finally 里
return:会吞掉正在传播的异常(见“常见陷阱”)。 else与finally都不是必需的,但else必须位于所有except之后、finally之前。
def read_file(filename):
file = None
try:
file = open(filename, "r", encoding="utf-8")
return file.read()
except FileNotFoundError:
return None
finally:
if file:
file.close() # 成功、失败、提前 return 都会关闭完整例子(数据库查询,展示执行顺序):
def query_database(sql):
db = Database("mydb")
try:
db.connect()
return db.query(sql) # return 的值会先暂存
except Exception as e:
print(f"查询失败:{e}")
return None
else:
print("查询成功") # try 无异常时执行
finally:
db.close() # 总是执行捕获所有异常
try:
risky_operation()
except Exception as e: # 捕获除 SystemExit/KeyboardInterrupt 之外的所有异常
print(f"发生错误:{type(e).__name__}: {e}")裸 except: 等价于 except BaseException:,会连 KeyboardInterrupt、SystemExit 一起吞掉,导致程序无法退出且拿不到异常对象,禁止使用。只有在最外层做兜底记录(并随后重新抛出)时才可以考虑 except Exception。
抛出异常:raise
def divide(a, b):
if b == 0:
raise ValueError("除数不能为0") # raise 异常类(参数)
return a / b重新抛出
处理完(如记日志)后让上层继续处理,用不带参数的 raise,它会保留原始异常的类型和堆栈:
def process_data(data):
try:
return risky_operation(data)
except ValueError as e:
print(f"记录错误:{e}")
raise # 原样向上抛抛出不同的异常 / 异常链 raise … from
把底层异常转换为对调用方更有意义的异常时,用 raise NewError(...) from e 保留原因链:
def get_user_age(user_id):
try:
user = database.get(user_id)
return user["age"]
except KeyError as e:
raise ValueError(f"用户 {user_id} 的年龄数据缺失") from eraise X from e→ 新异常的__cause__为 e,traceback 显示“The above exception was the direct cause of the following exception”。- 在 except 块内直接
raise X→ 新异常的__context__为 e,显示“During handling of the above exception, another exception occurred”。 raise X from None→ 抑制上下文,不显示原始异常。
常见内置异常类型
| 异常类型 | 触发场景 | 常见预防方式 |
|---|---|---|
ZeroDivisionError |
除数为 0(含空列表求平均) | 先判断分母是否为 0 |
ValueError |
类型对但值不合适:int("abc")、list.index 找不到、解包数量不匹配 |
校验格式与取值范围 |
TypeError |
类型不兼容:"a" + 1、len(123)、调用不可调用对象、格式化类型不匹配 |
检查/isinstance 校验类型 |
FileNotFoundError |
打开不存在的文件(OSError 子类) |
Path.exists() 或异常兜底用默认配置 |
PermissionError |
无读写权限(OSError 子类) |
检查权限与运行用户 |
KeyError |
访问字典不存在的键 | d.get(k, default) 或 if k in d |
IndexError |
列表/元组索引越界 | 先判断 0 <= i < len(seq) |
AttributeError |
访问不存在的属性/方法 | hasattr() 或 getattr(obj, name, default) |
ImportError / ModuleNotFoundError |
模块不存在或导入项不存在(后者为前者的子类,3.6+) | 确认已安装;可选依赖用 try 导入降级 |
UnicodeDecodeError |
用错编码解码文本(如 utf-8 读 gbk 文件) | 显式指定正确 encoding,或换编码重试 |
NameError |
使用未定义的变量 | 检查拼写与作用域 |
StopIteration |
迭代器耗尽(next() 无元素) |
for 循环自动处理;手工 next 用默认值参数 |
KeyboardInterrupt |
用户按下 Ctrl+C | 顶层捕获做收尾,不要随意吞掉 |
MemoryError / RecursionError |
内存不足 / 递归过深 | 优化算法、改为迭代、调 sys.setrecursionlimit |
json.JSONDecodeError |
JSON 文本格式非法 | 校验输入、给出格式错误提示 |
ConnectionError / TimeoutError |
网络连接失败 / 超时 | 重试 + 指数退避 |
典型触发场景速览:
int("二十岁") # ValueError:类型对但值无法解析
float("¥99.99") # ValueError:含非法字符
[1, 2, 3].index(10) # ValueError:值不在列表中
a, b = [1, 2, 3] # ValueError:too many values to unpack
"hello" + 5 # TypeError:str 与 int 不能相加
len(123) # TypeError:int 不是序列
"%d" % "hello" # TypeError:格式化类型不匹配
[1, 2, 3][10] # IndexError:索引越界
{"name": "张三"}["city"] # KeyError:键不存在
"hello".length # AttributeError:str 没有 length 属性
next(iter([])) # StopIteration:迭代器已耗尽捕获的典型写法:
try:
with open("nonexistent.txt", encoding="utf-8") as f:
content = f.read()
except FileNotFoundError:
content = "" # 文件缺失时用默认值
except PermissionError:
content = "" # 无权限
except UnicodeDecodeError:
with open("nonexistent.txt", encoding="gbk") as f:
content = f.read() # 换编码重试
except OSError as e: # 父类兜底
print(e.errno, e.filename)自定义异常
继承 Exception(不要继承 BaseException),用 __init__ 携带上下文,重写 __str__ 定制显示:
class InsufficientFundsError(Exception):
"""余额不足"""
pass
class ValidationError(Exception):
def __init__(self, field, message):
self.field = field # 额外上下文,便于调用方程序化处理
self.message = message
super().__init__(f"字段 '{field}' 验证失败:{message}")
def __str__(self): # 自定义字符串形式
return f"[{self.field}] {self.message}"使用:
class BankAccount:
def __init__(self, owner, balance=0):
self.owner, self.balance = owner, balance
def withdraw(self, amount):
if amount < 0:
raise ValidationError("amount", "取款金额不能为负数")
if amount > self.balance:
raise InsufficientFundsError(f"余额不足,当前 ¥{self.balance},需要 ¥{amount}")
self.balance -= amount
try:
BankAccount("张三", 1000).withdraw(1500)
except InsufficientFundsError as e:
print(f"取款失败:{e}")
except ValidationError as e:
print(f"验证失败:{e}(字段 {e.field})")要点:异常名以 Error 结尾;一处定义、多处复用;需要携带数据就定义 __init__;抛出时消息必须包含具体值和期望范围。
assert 断言
def apply_discount(price, discount):
assert 0 <= discount <= 1, f"折扣必须在 0~1 之间,收到 {discount}"
return price * (1 - discount)- 语法:
assert 条件, 可选消息,条件为假时抛AssertionError(消息)。 - 用于内部不变量自检、调试期假设,不要用于校验用户输入或生产数据。
python -O会移除所有assert语句,因此断言里不能包含有副作用的表达式(如assert save())。
安全类型转换与输入验证
用户输入千奇百怪("123"、"abc"、"12.5"、" 123 "、空串、None),逐项防御后统一兜底:
def parse_number(value, default=None, allow_float=False):
if value is None:
return default
text = str(value).strip()
if not text:
return default
try:
if allow_float:
return float(text)
return int(round(float(text))) if "." in text else int(text)
except ValueError:
return default # 解析失败用默认值,不抛给调用方交互式输入用循环重试,直到拿到合法值:
def get_integer(prompt, min_value=None, max_value=None):
while True:
try:
value = int(input(prompt))
if min_value is not None and value < min_value:
print(f"值必须 >= {min_value}")
continue
if max_value is not None and value > max_value:
print(f"值必须 <= {max_value}")
continue
return value
except ValueError:
print("请输入有效的整数")
except KeyboardInterrupt:
print("\n操作已取消")
return None上下文管理器
with 保证退出代码块时自动执行清理,等价于 try/finally:
with open("file.txt", encoding="utf-8") as f: # 离开 with 自动 close(),异常时也会关闭
content = f.read()
# 等价写法
f = open("file.txt", encoding="utf-8")
try:
content = f.read()
finally:
f.close()自定义上下文管理器只需实现两个方法:
class Timer:
def __enter__(self): # 进入 with 时调用,返回值绑定到 as 变量
self.start = time.time()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print(f"耗时 {time.time() - self.start:.3f}s")
return False # True 表示吞掉异常,False/None 让异常继续传播contextlib 提供的便捷工具:
from contextlib import contextmanager, suppress, closing
@contextmanager
def timer(): # 用生成器代替手写类
start = time.time()
try:
yield # yield 处执行 with 体
finally:
print(f"耗时 {time.time() - start:.3f}s")
with suppress(FileNotFoundError): # 忽略指定异常,替代 try/except: pass
os.remove("temp.txt")
with closing(connect()) as conn: # 对没有 __enter__ 的对象调用 .close()
conn.query(...)异常处理最佳实践
- 捕获具体异常,不用裸
except,不用过宽的except Exception掩盖 bug。 - 不吞异常:至少记日志;确实要忽略就用
contextlib.suppress并写明原因。 - 在合适的层级处理:底层函数不处理、让它抛出;在能给出用户提示或回滚操作的边界层(如控制器、main)统一处理。
- 提供有用的错误信息:包含出错的值与合法范围,如
raise ValueError(f"无效年龄:{age},必须在 0-150 之间")。 - 用
finally/with清理资源,不要靠“正常流程末尾关闭”。 - 记录日志而非 print,用
logging保留堆栈:
import logging, traceback
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
filename="app.log", encoding="utf-8",
)
try:
problematic_code()
except Exception:
logging.exception("处理数据失败") # 自动记录完整 traceback(级别 ERROR)
raise # 需要时继续向上抛调试时取堆栈字符串:
try:
problematic_code()
except Exception as e:
print(type(e).__name__, e) # 类型与消息
traceback.print_exc() # 直接打印完整堆栈
trace = traceback.format_exc() # 拿到字符串,可写入日志或上报实战例子
例1:安全的文件读取
def safe_read_file(filename, default=""):
"""读取文件,任何失败都返回默认值而不让程序崩溃"""
try:
with open(filename, "r", encoding="utf-8") as f:
return f.read()
except FileNotFoundError:
print(f"文件不存在:{filename}")
except PermissionError:
print(f"没有权限读取:{filename}")
except UnicodeDecodeError:
try:
with open(filename, "r", encoding="gbk") as f: # 换编码重试
return f.read()
except UnicodeDecodeError:
print(f"编码错误:{filename}")
except OSError as e:
print(f"读取失败:{e}")
return default例2:网络请求重试(指数退避)
import time, random
def fetch_data(url, max_retries=3, timeout=5):
for attempt in range(max_retries):
try:
if random.random() < 0.7: # 模拟 70% 失败率
raise ConnectionError("网络连接失败")
return {"status": "success", "data": [1, 2, 3]}
except ConnectionError as e:
if attempt == max_retries - 1: # 最后一次仍失败
print("达到最大重试次数")
return None
wait = 2 ** attempt # 1s, 2s, 4s 指数退避
print(f"连接失败:{e},{wait} 秒后重试")
time.sleep(wait)
except TimeoutError as e: # 超时不重试,直接失败
print(f"请求超时:{e}")
return None例3:批量处理,逐项容错
def process_batch(items):
"""单个失败不影响整批,分别记录成功与失败明细"""
results = {"success": [], "failed": []}
for i, item in enumerate(items):
try:
results["success"].append({"index": i, "result": process_item(item)})
except Exception as e:
results["failed"].append({
"index": i, "item": item,
"error": str(e), "type": type(e).__name__,
})
return results
def process_item(item):
if not isinstance(item, (int, float)):
raise ValueError(f"无效的数据类型:{type(item).__name__}")
if item < 0:
raise ValueError("值不能为负数")
return item * 2常见陷阱
| 陷阱 | 原因 | 解决 |
|---|---|---|
捕获后 pass 吞掉错误 |
问题被隐藏,无法定位 | 至少 logging.exception 记录,或 raise 继续抛出 |
| 捕获范围过大 | 裸 except: 连 KeyboardInterrupt、SystemExit 一起吞,程序无法正常退出 |
用 except Exception as e,业务上只捕获能处理的类型 |
| except 块中又出错 | 处理代码引用了未定义变量等,新异常覆盖原异常 | except 中只做简单、可靠的操作,如 str(e)、写日志 |
finally 中 return |
finally 的 return 会丢弃正在传播的异常和原返回值 | finally 只做清理,不写 return/break |
用 if 判断代替异常 |
“先检查后使用”(如先 exists() 再 open)存在竞态,且容易漏判 |
优先 EAFP:直接 try,用 except 处理失败 |
| 异常用于流程控制 | 用异常实现正常分支,开销大、可读性差 | 正常分支用 if/dict.get,异常只留给异常情况 |
| 异常顺序写反 | 父类 except 在前,子类分支永不执行 | 子类写在父类之前 |
| 循环中重复捕获太宽 | 把整段循环包进 try,一处失败无法定位到具体项 | 把 try 放在循环体内,逐项处理并记录 |
assert 校验用户输入 |
-O 模式下断言被移除,校验失效 |
显式 raise ValueError/TypeError |
| 丢失原始堆栈 | raise NewError() 丢掉了底层原因 |
用 raise NewError(...) from e 保留异常链 |
要点
try→except(按子类优先、从上到下匹配)→else(无异常时)→finally(必然执行,只做清理,禁止return)。- 只捕获你能处理的异常,用
as e取信息;禁止裸except:;禁止except: pass吞异常,至少logging.exception记录。 raise主动抛出、raise原样重抛、raise X from e保留异常链转换类型;消息要包含具体值与合法范围。- 自定义异常继承
Exception,按需实现__init__携带上下文、__str__定制显示;类名以Error结尾。 - 优先用
with/contextlib管理资源,而不是手写 try/finally;assert只用于内部不变量自检,不做输入校验。 - 高频异常对照:
ValueError(值不合适)、TypeError(类型不对)、KeyError/IndexError(键/索引不存在)、FileNotFoundError/PermissionError(文件与权限)、ImportError(导入失败)。
面向对象基础
核心概念与定义
- 类(Class):对象的模板;对象:类的实例;属性:数据;方法:行为
- 把相关数据和功能打包进对象,解决“数据分散、变量爆炸、难以维护”的问题
class Dog:
"""狗类"""
def __init__(self, name, age): # 构造方法,创建对象时自动调用
self.name = name # 实例属性
self.age = age
def bark(self): # 实例方法,第一个参数必须是 self
print(f"{self.name} 说:汪汪汪!")
dog = Dog("旺财", 3)
dog.bark() # 旺财 说:汪汪汪!
dog.age = 4 # 直接改属性
dog.color = "黑" # 可动态添加属性(不推荐)self 代表实例本身,只是约定名(换成 this 也能跑,但不要这么做)。三大特性:封装、继承、多态(后两者见第 21 章)。
面向过程按步骤组织,面向对象按“谁有什么数据和行为”组织:简单脚本用面向过程,大型/多人协作项目用面向对象,实际项目两者结合。
# 面向过程:100 个矩形要 200 个变量
width1, height1 = 10, 5
area1 = width1 * height1
class Rectangle: # 面向对象:数据与行为在一起
def __init__(self, width, height):
self.width, self.height = width, height
def area(self): return self.width * self.height
rects = [Rectangle(10, 5), Rectangle(20, 8)]
print(sum(r.area() for r in rects))__init__ 与初始化
class Student:
def __init__(self, name, age=18, grade="一年级"): # 默认参数
self.name = name
self.age = age
self.grade = grade
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
if balance < 0: # 初始化时校验
raise ValueError("初始余额不能为负数")
self.balance = balanceself 的作用:方法内通过它读写实例自己的状态,实例之间互不影响。
class Counter:
def __init__(self, start=0):
self.count = start
def increment(self): self.count += 1
c1, c2 = Counter(10), Counter()
c1.increment(); c1.increment()
c1.count, c2.count # (12, 0)属性与方法速查
| 类型 | 定义位置 | 特点 |
|---|---|---|
| 实例属性 | self.x = ...(通常在 __init__) |
每个实例独立 |
| 类属性 | 类体内、__init__ 外 |
所有实例共享,用 类名.x 修改 |
| 实例方法 | def f(self) |
可访问 self |
| 类方法 | @classmethod def f(cls) |
访问/修改类属性、作工厂方法 |
| 静态方法 | @staticmethod def f() |
不访问实例/类,只是挂靠在类上的普通函数 |
class Person:
count = 0 # 类属性
def __init__(self, name):
self.name = name # 实例属性
Person.count += 1
@classmethod
def get_count(cls): # cls 指类本身,支持继承
return cls.count
@classmethod
def create_anonymous(cls): # 类方法作工厂
return cls("匿名用户")
@staticmethod
def is_adult(age): # 无需 self/cls
return age >= 18类属性注意:给实例同名赋值只会创建实例属性遮蔽它,不会改类属性(Example.class_var 不变)。
class Car:
wheels = 4 # 类属性:所有实例共享
vehicle_count = 0
def __init__(self, brand):
self.brand = brand # 实例属性:每个实例独立
Car.vehicle_count += 1
def accelerate(self, increase):
self.speed = getattr(self, "speed", 0) + increase
c1, c2 = Car("Toyota"), Car("Honda")
c1.wheels # 4
Car.wheels = 6 # 改类属性,所有实例可见
c1.wheels # 6
c1.wheels = 8 # 只给 c1 建了实例属性
c2.wheels # 6(类属性未变)
Car.vehicle_count # 2私有属性与名称改写
class BankAccount:
def __init__(self, balance):
self._balance = balance # 单下划线:约定私有,仍可访问
self.__pin = "1234" # 双下划线:名称改写
def __verify(self, pin): # 私有方法
return pin == self.__pin
a = BankAccount(1000)
a._balance # 1000(能访问,但不该访问)
a.__pin # AttributeError
a._BankAccount__pin # '1234'(改写后的真实名字,别这么用)__name 被改写为 _类名__name,只是“防误用”不是真安全。
魔术方法速查
| 方法 | 触发 | 说明 |
|---|---|---|
__init__(self, ...) |
Cls(...) |
构造初始化 |
__str__ |
print(obj)、str() |
用户友好表示 |
__repr__ |
repr()、交互式回显 |
开发者友好,尽量可 eval 还原 |
__len__ |
len(obj) |
必须返回非负 int |
__eq__ / __lt__ / __le__ |
== / < / <= |
定义 __eq__ 通常要同时定义 __hash__ |
__getitem__ / __setitem__ |
obj[i] / obj[i] = v |
支持下标与切片,可做序列/映射 |
__iter__ / __next__ |
for、迭代 |
可迭代对象 / 迭代器 |
__contains__ |
x in obj |
成员判断 |
__call__ |
obj(...) |
对象像函数一样调用 |
__enter__ / __exit__ |
with obj as x |
上下文管理,负责资源获取与释放 |
class Point:
def __init__(self, x, y):
self.x, self.y = x, y
def __str__(self): return f"Point({self.x}, {self.y})"
def __repr__(self): return f"Point(x={self.x}, y={self.y})"
def __eq__(self, other):
return (self.x, self.y) == (other.x, other.y)
class Multiplier:
def __init__(self, factor): self.factor = factor
def __call__(self, value): return value * self.factor # double(5) -> 10
class Playlist:
def __init__(self): self.songs = []
def add_song(self, song): self.songs.append(song)
def __len__(self): return len(self.songs) # len(playlist)
class MyList: # 支持下标访问
def __init__(self): self.items = []
def append(self, item): self.items.append(item)
def __getitem__(self, index): return self.items[index]
def __setitem__(self, index, value): self.items[index] = value
class Timer: # 上下文管理器
def __enter__(self):
import time; self.start = time.time(); return self
def __exit__(self, exc_type, exc, tb):
import time; print(f"耗时 {time.time() - self.start:.2f}s")
return False # 返回 True 会吞掉异常只实现 __getitem__ 也能让对象支持 for 循环和 in;定义 __eq__ 后记得同步 __hash__,否则对象不可哈希。
例子
银行账户系统(类属性 + 实例方法 + __str__)
class BankAccount:
interest_rate = 0.03 # 类属性
account_count = 0
def __init__(self, owner, balance=0):
self.owner = owner
self.balance = balance
self.transactions = []
BankAccount.account_count += 1
def deposit(self, amount):
if amount <= 0:
return False
self.balance += amount
self.transactions.append(f"存款 +¥{amount}")
return True
def withdraw(self, amount):
if amount <= 0 or amount > self.balance:
return False
self.balance -= amount
self.transactions.append(f"取款 -¥{amount}")
return True
def transfer(self, target, amount):
if self.withdraw(amount):
target.deposit(amount)
self.transactions.append(f"转账给 {target.owner} -¥{amount}")
return True
return False
def add_interest(self):
interest = self.balance * self.interest_rate
self.balance += interest
self.transactions.append(f"利息 +¥{interest:.2f}")
def show_transactions(self):
for i, t in enumerate(self.transactions, 1):
print(f"{i}. {t}")
print(f"当前余额:¥{self.balance}")
def __str__(self):
return f"账户[{self.owner}]: ¥{self.balance}"
a1, a2 = BankAccount("张三", 1000), BankAccount("李四", 500)
a1.deposit(500); a1.withdraw(200); a1.transfer(a2, 300); a1.add_interest()
print(a1) # 账户[张三]: ...
print(BankAccount.account_count) # 2购物车系统(组合:购物车持有商品对象)
class Product:
def __init__(self, name, price, stock):
self.name, self.price, self.stock = name, price, stock
def __str__(self):
return f"{self.name} - ¥{self.price} (库存:{self.stock})"
class ShoppingCart:
def __init__(self, owner):
self.owner = owner
self.items = [] # [(product, quantity), ...]
def add_item(self, product, quantity=1):
if quantity <= 0 or quantity > product.stock:
return False
for i, (item, qty) in enumerate(self.items):
if item.name == product.name:
if qty + quantity > product.stock:
return False
self.items[i] = (item, qty + quantity)
return True
self.items.append((product, quantity))
return True
def update_quantity(self, product_name, quantity):
if quantity <= 0:
return self.remove_item(product_name)
for i, (product, _) in enumerate(self.items):
if product.name == product_name:
if quantity > product.stock:
return False
self.items[i] = (product, quantity)
return True
return False
def remove_item(self, product_name):
for i, (product, _) in enumerate(self.items):
if product.name == product_name:
self.items.pop(i)
return True
return False
def get_total(self):
return sum(p.price * qty for p, qty in self.items)
def show_cart(self):
for p, qty in self.items:
print(f"{p.name:15s} ¥{p.price:6.2f} x{qty:2d} = ¥{p.price * qty:8.2f}")
print(f"{'总计':>30s} ¥{self.get_total():8.2f}")
def checkout(self):
if not self.items:
return False
if any(qty > p.stock for p, qty in self.items):
return False
for p, qty in self.items:
p.stock -= qty # 结账才真正扣库存
total = self.get_total()
self.items = []
return True图书管理系统(对象组合 + 状态维护)
class Book:
def __init__(self, title, author, isbn, copies=1):
self.title, self.author, self.isbn = title, author, isbn
self.total_copies = self.available_copies = copies
def borrow(self):
if self.available_copies <= 0:
return False
self.available_copies -= 1
return True
def return_book(self):
if self.available_copies >= self.total_copies:
return False
self.available_copies += 1
return True
def __str__(self):
return f"《{self.title}》- {self.author}"
class Library:
def __init__(self, name):
self.name = name
self.books = []
self.borrowed_records = {} # {用户: [Book]}
def add_book(self, book):
self.books.append(book)
def find_book(self, title):
return next((b for b in self.books if b.title == title), None)
def show_books(self):
for b in self.books:
print(f"{b} - {b.available_copies}/{b.total_copies} 可借")
def borrow_book(self, user, title):
book = self.find_book(title)
if not book or not book.borrow():
return False
self.borrowed_records.setdefault(user, []).append(book)
return True
def return_book(self, user, title):
for i, book in enumerate(self.borrowed_records.get(user, [])):
if book.title == title:
book.return_book()
self.borrowed_records[user].pop(i)
return True
return False常见陷阱
- 忘记写
self:def method():调用时报TypeError(实际收到实例参数)。→ 实例方法首参永远是self。 - 用可变类属性当容器:
items = []定义在类体内被所有实例共享,obj1的 append 会污染obj2。→ 在__init__里self.items = []。 - 可变默认参数:
def __init__(self, items=[])的列表在函数定义时创建一次,所有实例共用。→items=None,再self.items = items if items is not None else []。 - 误以为
self.x = ...修改了类属性:只会新建实例属性。→ 改类属性用类名.x = ...。 - 定义了
__eq__却没定义__hash__:对象变成不可哈希,无法放进set/dict键。→ 同时定义,或设__hash__ = None明确表示不可哈希。
要点
- 类是模板、对象是实例;
self是实例自身,实例方法首参必须有它。 - 实例属性独立、类属性共享;
@classmethod拿cls,@staticmethod两者都不拿。 _x是约定私有,__x触发名称改写为_类名__x,只能防误用。- 魔术方法让自定义类像内置类型一样工作:
__str__/__repr__/__len__/__eq__/__getitem__/__call__/__enter__。 - 优先用组合(一个类持有另一个类的实例)而非层层继承来复用功能。
- 三大高频坑:漏
self、可变类属性共享、可变默认参数。
面向对象进阶
继承
子类获得父类的属性和方法,只写自己特有的部分,从而复用代码、统一修改。
class 父类名: # 基类 / 超类(Base、Superclass)
pass
class 子类名(父类名): # 派生类(Derived、Subclass)
passclass Animal:
def __init__(self, name, age):
self.name, self.age = name, age
def eat(self): print(f"{self.name}在吃东西")
def sleep(self): print(f"{self.name}在睡觉")
def speak(self): return "某种声音" # 供子类重写
class Dog(Animal):
def speak(self): # 方法重写(Override)
return f"{self.name}说: 汪汪!"
def wag_tail(self): print(f"{self.name}摇尾巴")
class Bird(Animal):
def __init__(self, name, age, can_fly=True):
super().__init__(name, age) # 先初始化父类部分
self.can_fly = can_fly # 再写自己的属性
def fly(self):
print(f"{self.name}飞翔" if self.can_fly else f"{self.name}不会飞")| 术语 | 英文 | 说明 | 例子 |
|---|---|---|---|
| 父类 | Parent / Base / Superclass | 被继承的类 | Animal |
| 子类 | Child / Derived / Subclass | 继承的类 | Dog, Cat |
| 单继承 | Single Inheritance | 一个子类只有一个父类 | Dog(Animal) |
| 多重继承 | Multiple Inheritance | 一个子类有多个父类 | Duck(Flyable, Swimmable) |
| 方法重写 | Method Override | 子类重新定义父类方法 | Dog.speak() 重写 Animal.speak() |
| 方法扩展 | Method Extension | 子类扩展父类方法 | super().f() 后再加新逻辑 |
super()
super() 返回 MRO 中的下一个类,用来调用父类方法(不只是 __init__)。
class Student(Person):
def __init__(self, name, age, student_id):
super().__init__(name, age) # 不写则父类属性未初始化
self.student_id = student_id
class Square(Rectangle):
def __init__(self, side):
super().__init__(side, side) # 正方形是特殊的矩形
def area(self):
base = super().area() # 复用父类逻辑再扩展
return base多重继承与 MRO
class Flyable:
def fly(self): print(f"{self.name}在飞")
class Swimmable:
def swim(self): print(f"{self.name}在游泳")
class Duck(Flyable, Swimmable): # 按顺序从左到右查找
def __init__(self, name): self.name = namePython 用 C3 线性化得到方法解析顺序(MRO),同名方法按 类名.__mro__ 的顺序取第一个。
class A:
def method(self): print("A")
class B(A):
def method(self): print("B"); super().method()
class C(A):
def method(self): print("C"); super().method()
class D(B, C):
def method(self): print("D"); super().method()
D().method() # D B C A(不是 D B A)
D.__mro__ # (D, B, C, A, object)super() 不是“调用父类”,而是“沿 MRO 往后走”,这是菱形继承中每个类只执行一次的关键。
多态与鸭子类型
同一接口、不同实现;调用方只关心方法名,不关心具体类型。
class Shape:
def area(self): raise NotImplementedError
class Rectangle(Shape):
def __init__(self, w, h): self.w, self.h = w, h
def area(self): return self.w * self.h
class Circle(Shape):
def __init__(self, r): self.r = r
def area(self): return 3.14159 * self.r ** 2
for s in [Rectangle(5, 3), Circle(4)]: # 统一处理
print(s.area())鸭子类型:只要对象有对应方法就能用,无需公共基类或接口声明。
def make_it_quack(thing):
thing.quack() # 不检查类型,只看有没有 quack
make_it_quack(Duck()) # 嘎嘎嘎
make_it_quack(Person()) # 人模仿鸭子叫封装与 property
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self.__balance = balance # 双下划线:名称改写为 _BankAccount__balance
def deposit(self, amount):
if amount > 0:
self.__balance += amount
return True
return False
def get_balance(self): # 通过公开方法访问
return self.__balance@property 让“方法”用起来像“属性”,同时保留校验与只读控制。
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self): return self._radius
@radius.setter
def radius(self, value):
if value < 0:
raise ValueError("半径不能为负数")
self._radius = value
@property
def area(self): return 3.14159 * self._radius ** 2 # 只有 getter -> 只读
@property
def diameter(self): return 2 * self._radius
c = Circle(5)
c.radius = 10 # 走 setter,可校验
c.area = 100 # AttributeError:只读属性常用于取值/赋值时夹取范围,例如 HP 限制在 [0, max_hp]:
@property
def hp(self): return self._hp
@hp.setter
def hp(self, value): self._hp = max(0, min(value, self._max_hp))抽象基类(abc)
ABC + @abstractmethod 定义接口:抽象类不能实例化,子类必须实现所有抽象方法。
from abc import ABC, abstractmethod
class Animal(ABC):
def __init__(self, name): self.name = name
@abstractmethod
def speak(self): pass
@abstractmethod
def move(self): pass
class Dog(Animal):
def speak(self): return f"{self.name}说: 汪汪!"
def move(self): return f"{self.name}在跑"
Animal("x") # TypeError:抽象类不能实例化若不想引入 abc,也可在基类方法里 raise NotImplementedError("子类必须实现...") 作为软约束。
例子
员工管理系统(多层继承 + super() + 多态)
from datetime import date
class Employee:
employee_count = 0
def __init__(self, name, employee_id, hire_date):
self.name, self.employee_id, self.hire_date = name, employee_id, hire_date
Employee.employee_count += 1
def get_info(self):
return f"员工: {self.name} (ID: {self.employee_id})"
def calculate_salary(self): # 由子类实现
raise NotImplementedError("子类必须实现calculate_salary方法")
def years_of_service(self):
return date.today().year - self.hire_date.year
class SalariedEmployee(Employee):
def __init__(self, name, employee_id, hire_date, monthly_salary):
super().__init__(name, employee_id, hire_date)
self.monthly_salary = monthly_salary
def calculate_salary(self): return self.monthly_salary
def get_info(self): # 扩展父类方法
return f"{super().get_info()} - 固定月薪: ¥{self.monthly_salary}"
class HourlyEmployee(Employee):
def __init__(self, name, employee_id, hire_date, hourly_rate):
super().__init__(name, employee_id, hire_date)
self.hourly_rate = hourly_rate
self.hours_worked = 0
def log_hours(self, hours): self.hours_worked += hours
def calculate_salary(self):
salary = self.hours_worked * self.hourly_rate
self.hours_worked = 0 # 结算后重置
return salary
class Manager(SalariedEmployee):
def __init__(self, name, employee_id, hire_date, monthly_salary, department):
super().__init__(name, employee_id, hire_date, monthly_salary)
self.department = department
self.team = []
def add_team_member(self, emp): self.team.append(emp)
def calculate_salary(self):
return super().calculate_salary() + len(self.team) * 500 # 底薪 + 团队奖金
employees = [
SalariedEmployee("张三", "E001", date(2020, 1, 15), 8000),
HourlyEmployee("李四", "E002", date(2021, 3, 10), 50),
Manager("王五", "M001", date(2019, 6, 1), 15000, "技术部"),
]
employees[1].log_hours(160)
for emp in employees: # 多态:统一调用各自实现
print(emp.get_info(), emp.calculate_salary(), emp.years_of_service())银行账户系统(抽象基类 + property + 模板方法)
from abc import ABC, abstractmethod
from datetime import datetime
class Account(ABC):
account_number_counter = 1000
def __init__(self, owner, balance=0):
self.account_number = Account.account_number_counter
Account.account_number_counter += 1
self.owner = owner
self._balance = balance
self.transactions = []
self._log_transaction("开户", balance)
@property
def balance(self): return self._balance
def _log_transaction(self, type_, amount, balance=None):
self.transactions.append({
"time": datetime.now(), "type": type_,
"amount": amount, "balance": self._balance if balance is None else balance,
})
def deposit(self, amount):
if amount <= 0: return False
self._balance += amount
self._log_transaction("存款", amount)
return True
def withdraw(self, amount):
if amount <= 0 or not self._can_withdraw(amount): # 钩子交给子类
return False
self._balance -= amount
self._log_transaction("取款", amount)
return True
@abstractmethod
def _can_withdraw(self, amount): pass # 能否取款由子类决定
@abstractmethod
def calculate_interest(self): pass
class SavingsAccount(Account):
def __init__(self, owner, balance=0, interest_rate=0.03):
super().__init__(owner, balance)
self.interest_rate = interest_rate
def _can_withdraw(self, amount): return amount <= self._balance # 不可透支
def calculate_interest(self):
interest = self._balance * self.interest_rate
self._balance += interest
self._log_transaction("利息", interest)
return interest
class CheckingAccount(Account):
def __init__(self, owner, balance=0, overdraft_limit=1000):
super().__init__(owner, balance)
self.overdraft_limit = overdraft_limit
def _can_withdraw(self, amount): # 可透支
return amount <= self._balance + self.overdraft_limit
def calculate_interest(self): return 0
def get_overdraft_amount(self): return abs(self._balance) if self._balance < 0 else 0游戏角色系统(property 夹取 + 子类差异化)
import random
class Character:
def __init__(self, name, level=1):
self.name, self.level = name, level
self._max_hp, self._max_mp = 100, 50
self._hp, self._mp = self._max_hp, self._max_mp
self.attack, self.defense = 10, 5
@property
def hp(self): return self._hp
@hp.setter
def hp(self, v): self._hp = max(0, min(v, self._max_hp)) # 自动夹取范围
@property
def mp(self): return self._mp
@mp.setter
def mp(self, v): self._mp = max(0, min(v, self._max_mp))
def is_alive(self): return self.hp > 0
def take_damage(self, damage):
self.hp -= max(1, damage - self.defense) # 走 setter 保证不越界
if not self.is_alive(): print(f"{self.name} 被击败了!")
class Warrior(Character):
def __init__(self, name, level=1):
super().__init__(name, level)
self._max_hp = self._hp = 150
self.attack, self.defense = 15, 10
def power_strike(self, target):
if self.mp < 10: return
self.mp -= 10
target.take_damage(self.attack * 2)
class Mage(Character):
def __init__(self, name, level=1):
super().__init__(name, level)
self._max_hp, self._max_mp = 80, 100
self._hp, self._mp = 80, 100
self.magic_power = 20
def fireball(self, target):
if self.mp < 15: return
self.mp -= 15
target.take_damage(self.magic_power + random.randint(5, 15))常见陷阱
- 子类忘记调用
super().__init__():父类属性没初始化,访问时报AttributeError。→ 子类__init__第一行调用super().__init__(...)。 - 菱形继承以为
super()只调父类:实际按 MRO 走,D(B, C)中B的super()会走到C而不是A。→ 用类名.__mro__确认顺序,各环节的super()都要保留。 - 重写方法时签名不一致:参数个数/名字对不上,调用方传参即报错。→ 子类方法参数与父类保持一致(LSP)。
__balance在子类里访问不到:名称改写基于定义它的类名。→ 子类通过父类提供的property或公开方法访问。@property方法里做重计算或产生副作用:每次属性访问都执行,成本高且难以察觉。→ 只做轻量返回,需要缓存用functools.cached_property。- 只定义
@property又想赋值:只读属性赋值抛AttributeError。→ 补上@x.setter。
设计原则速记
- 组合优于继承:只是“用得到”某类功能时用组合(持有实例),只有真正是“is-a”关系才继承,避免继承链过深。
- 单一职责:一个类只做一件事,方法短小。
- 开放封闭:扩展靠新增子类,不改已有类。
- 里氏替换:子类必须能替换父类而不破坏调用方(不收紧前置条件、不弱化后置条件)。
- 依赖抽象:面向基类/抽象接口编程,而非具体类。
- 接口隔离:宁可多个小而专的抽象类,也不要一个臃肿的父类。
要点
- 继承复用父类代码,子类只写差异;方法重写改行为,
super()复用父类逻辑。 super()沿 MRO 往后走,多重继承下不是简单“调父类”;查顺序用类名.__mro__。- 多态靠“同名方法不同实现”;Python 的鸭子类型只看有没有该方法,不要求公共基类。
- 封装用
__x(名称改写)+@property(读写控制、校验、只读属性)。 abc.ABC+@abstractmethod定义接口,抽象类不可实例化,子类必须实现全部抽象方法。- 能用组合就别用继承;继承表达“是一种”,组合表达“有一个”。
常用内置模块
标准库随 Python 安装,import 即用。下面按「一句话用途 + 最小示例」组织,先给速查总表。
模块速查总表
| 模块 | 用途 | 常用 API |
|---|---|---|
os |
目录/文件/环境变量/系统命令 | getcwd listdir makedirs rename remove environ;os.path.join/exists/isfile/isdir/getsize/dirname/basename |
sys |
解释器与运行时信息 | argv version_info platform path exit getsizeof stdout |
math |
数学函数与常量 | sqrt pow floor ceil factorial gcd pi e isnan |
random |
伪随机数 | randint randrange random uniform choice choices sample shuffle seed |
datetime |
日期时间 | datetime.now/strptime/strftime date.today timedelta timezone.utc |
time |
时间戳、休眠、计时 | time sleep ctime localtime strftime perf_counter |
collections |
增强容器 | Counter defaultdict namedtuple deque OrderedDict |
itertools |
迭代器工具 | count cycle repeat chain compress permutations combinations product groupby |
functools |
函数工具 | lru_cache partial reduce wraps |
re |
正则匹配(详见第 23 章) | match search findall finditer sub split compile |
json |
JSON 编解码 | dumps/loads(ensure_ascii=False 保留中文)dump/load |
csv |
CSV 读写 | reader writer DictReader DictWriter |
subprocess |
调用外部程序 | run(..., capture_output=True, text=True) Popen |
logging |
分级日志 | basicConfig getLogger;debug/info/warning/error/critical |
argparse |
命令行参数解析 | ArgumentParser add_argument parse_args |
shutil |
高层文件操作 | copy copy2 move rmtree disk_usage make_archive |
glob |
通配符查找文件 | glob.glob("**/*.py", recursive=True) iglob |
tempfile |
临时文件/目录 | NamedTemporaryFile TemporaryDirectory gettempdir |
pathlib |
面向对象路径(推荐替代 os.path) |
Path.cwd/home / 拼接 read_text/write_text glob/rglob mkdir |
datetime · 日期时间
from datetime import datetime, date, time, timedelta
now = datetime.now() # 2024-01-15 14:30:45.123456
today = date.today() # 2024-01-15
datetime.now().time() # 14:30:45.123456
now.year, now.month, now.day, now.hour, now.weekday() # weekday: 0=周一 … 6=周日
datetime(2000, 5, 20, 10, 30) # 构造
datetime.combine(date(2024,1,15), time(14,30)) # 日期+时间合并格式化与解析:strftime 对象→字符串,strptime 字符串→对象,格式码须一一对应。
now.strftime("%Y-%m-%d %H:%M:%S") # 2024-01-15 14:30:45
now.strftime("%Y年%m月%d日 %A") # 2024年01月15日 Monday
datetime.strptime("2024-01-15 14:30:45", "%Y-%m-%d %H:%M:%S")
datetime.strptime("Jan 15, 2024", "%b %d, %Y")| 代码 | 含义 | 代码 | 含义 |
|---|---|---|---|
%Y / %y |
四位 / 两位年 | %H / %I |
24 时制 / 12 时制小时 |
%m / %d |
月 / 日 | %M / %S |
分 / 秒 |
%A / %a |
星期全称 / 简称 | %p |
AM/PM |
%B / %b |
月份全称 / 简称 | %w |
星期数 0-6(0=周日) |
计算:用 timedelta 做加减,两个 datetime 相减得 timedelta。
now + timedelta(days=1, hours=2) # 也支持 weeks/minutes/seconds
(end - start).days # 相差天数
(end - start).total_seconds() # 相差总秒数collections · 专业容器
Counter — 计数。
from collections import Counter
c = Counter(["apple", "banana", "apple"])
c["apple"] # 2;不存在的 key 返回 0 而不报错
c.most_common(2) # [('apple', 2), ('banana', 1)]
c.update(["apple"]); c.subtract(["apple"])
sum(c.values()) # 总数
Counter("hello").most_common(1) # [('l', 2)]defaultdict — 带默认值的字典,省掉 if key not in d。
from collections import defaultdict
groups = defaultdict(list); groups["fruits"].append("apple") # 无 KeyError
counts = defaultdict(int); counts[w] += 1 # 计数
tags = defaultdict(set); tags["python"].add("web") # 去重集合namedtuple — 命名字段的元组,可索引也可属性访问。
from collections import namedtuple
Person = namedtuple("Person", ["name", "age"])
p = Person("张三", 25)
p.name, p[0] # ('张三', '张三')
p._asdict() # {'name': '张三', 'age': 25}
p._replace(age=26) # 返回新对象deque — 双端队列,两端增删都是 O(1),list.pop(0) 是 O(n)。
from collections import deque
q = deque([1, 2, 3], maxlen=3) # maxlen 满后自动丢弃另一端
q.append(4); q.appendleft(0); q.pop(); q.popleft()
q.rotate(1) # 右旋;负数左旋| 容器 | 特点 | 典型场景 |
|---|---|---|
Counter |
元素计数,缺失返回 0 | 词频、投票统计 |
defaultdict |
缺失 key 自动建默认值 | 分组、累加 |
namedtuple |
命名字段的不可变元组 | 轻量数据记录(替代纯元组) |
deque |
双端 O(1) 增删、可限长 | 队列/栈、滑动窗口、最近 N 条记录 |
random · 随机数
import random
random.randint(1, 10) # [1,10] 闭区间整数
random.randrange(0, 10, 2) # 0,2,4,6,8
random.random() # [0.0,1.0) 浮点
random.uniform(1.5, 5.5) # 区间浮点
random.choice(seq) # 随机取一个
random.sample(seq, 2) # 不重复抽样
random.choices(seq, weights=[5,3,2], k=10) # 可重复 + 权重
random.shuffle(lst) # 原地打乱
random.seed(42) # 固定种子 → 结果可复现(调试/测试用)安全场景(密码、令牌)用 secrets 模块,不用 random。
itertools · 迭代工具
import itertools
itertools.count(10, 2) # 无限等差:10,12,14…
list(zip(itertools.count(1), names)) # 生成编号
itertools.cycle(["red","green"]) # 无限循环
itertools.repeat(5, 3) # 重复 3 次
list(itertools.permutations([1,2,3], 2)) # 排列(考虑顺序)
list(itertools.combinations([1,2,3], 2)) # 组合(不考虑顺序)
list(itertools.product(["red","blue"], "SM"))# 笛卡尔积
list(itertools.chain([1,2], [3,4])) # [1,2,3,4]
list(itertools.compress("ABCDE", [1,0,1,0,1])) # ['A','C','E']
list(itertools.takewhile(lambda x: x < 6, [1,3,6,7]))# [1,3]
list(itertools.dropwhile(lambda x: x < 6, [1,3,6,7]))# [6,7]
for k, g in itertools.groupby(data, key=lambda x: x[0]): # 分组前必须先排序
print(k, list(g))functools · 函数工具
from functools import lru_cache, partial, reduce
@lru_cache(maxsize=None) # 缓存递归结果,fib(30) 从秒级降到微秒级
def fib(n):
return n if n < 2 else fib(n-1) + fib(n-2)
fib.cache_info() # 命中统计;fib.cache_clear() 清空
square = partial(pow, exponent=2) # 固定部分参数
error_log = partial(print, "[ERROR]")
reduce(lambda x, y: x * y, [1,2,3,4,5]) # 120,累积归约pathlib · 现代路径
from pathlib import Path
p = Path("data") / "files" / "example.txt" # 用 / 拼接,跨平台
Path.cwd(); Path.home()
p.exists(); p.is_file(); p.is_dir()
p.mkdir(parents=True, exist_ok=True)
p.write_text("hi", encoding="utf-8"); p.read_text(encoding="utf-8")
p.name, p.stem, p.suffix, p.parent, p.parts # example.txt / example / .txt / data/files
Path(".").glob("*.py"); Path(".").rglob("*.py") # 单层 / 递归匹配os 与 sys · 系统接口
import os, sys
os.getcwd(); os.listdir("."); os.makedirs("a/b/c", exist_ok=True)
os.rename("old.txt", "new.txt"); os.remove("f.txt")
os.environ.get("PATH") # 环境变量
os.path.join("folder", "file.txt") # 跨平台拼接
os.path.exists/isfile/isdir/getsize/getmtime("f.txt")
os.path.dirname("/home/u/a.txt") # /home/u
sys.argv # ['script.py', 'arg1'],命令行参数
sys.version_info; sys.platform # win32 / linux / darwin
sys.path # 模块搜索路径
sys.getsizeof(obj) # 对象占用字节
sys.exit(1) # 非 0 表示异常退出其他常用标准库
import math
math.sqrt(16); math.floor(3.7); math.ceil(3.2); math.factorial(5)
math.gcd(12, 18); math.pi; math.e; math.isnan(float("nan"))import time
time.time() # Unix 时间戳(秒)
start = time.perf_counter(); ...; time.perf_counter() - start # 高精度计时
time.sleep(0.5) # 休眠
time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())import re # 详见第 23 章
re.findall(r"\d+", "a1b22") # ['1', '22']
re.sub(r"\s+", " ", text) # 替换import json
json.dumps({"name": "张三"}, ensure_ascii=False) # '{"name": "张三"}'
json.loads(s); json.dump(data, f); json.load(f) # 文件读写import csv
with open("d.csv", newline="", encoding="utf-8") as f:
rows = list(csv.reader(f)) # 每行是 list
with open("d.csv", "w", newline="", encoding="utf-8") as f:
csv.writer(f).writerows(rows)
with open("d.csv", encoding="utf-8") as f:
for row in csv.DictReader(f): row["name"] # 每行是 dictimport subprocess
r = subprocess.run(["git", "status"], capture_output=True, text=True, check=True)
r.returncode, r.stdout, r.stderr # 优先用列表传参,避免 shell 注入import logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
logging.info("启动完成") # 用 logging 而非 print 输出运行信息import argparse
p = argparse.ArgumentParser(description="示例")
p.add_argument("--name", required=True); p.add_argument("--times", type=int, default=1)
args = p.parse_args() # python app.py --name Tom --times 3import shutil, glob, tempfile
shutil.copy2("a.txt", "backup/"); shutil.move("a.txt", "b.txt")
shutil.rmtree("tmp_dir"); shutil.make_archive("bak", "zip", "data")
glob.glob("**/*.py", recursive=True) # 递归查找
with tempfile.TemporaryDirectory() as d: ... # 退出自动清理实用例子
例子1:文件整理工具(pathlib + shutil)
from pathlib import Path
import shutil
from datetime import datetime
TYPES = {"图片": [".jpg", ".png", ".gif"],
"文档": [".txt", ".pdf", ".docx"],
"视频": [".mp4", ".mkv"]}
def organize(src):
src = Path(src)
for file in src.iterdir():
if not file.is_file():
continue
cat = next((c for c, exts in TYPES.items() if file.suffix.lower() in exts), None)
if not cat:
continue
dest_dir = src / cat
dest_dir.mkdir(exist_ok=True)
dest = dest_dir / file.name
if dest.exists(): # 重名加时间戳
dest = dest_dir / f"{dest.stem}_{datetime.now():%Y%m%d_%H%M%S}{dest.suffix}"
shutil.move(str(file), str(dest))
print(f"移动: {file.name} -> {cat}/")
# organize("Downloads")例子2:日程提醒系统(datetime + defaultdict)
from datetime import datetime, timedelta
from collections import defaultdict
class ScheduleReminder:
def __init__(self):
self.schedules = defaultdict(list)
def add(self, date_str, time_str, event):
try:
dt = datetime.strptime(f"{date_str} {time_str}", "%Y-%m-%d %H:%M")
except ValueError as e:
return print(f"日期格式错误: {e}")
self.schedules[date_str].append({"time": time_str, "event": event, "dt": dt})
def upcoming(self, days=7):
now = datetime.now()
items = [s for lst in self.schedules.values() for s in lst
if now < s["dt"] <= now + timedelta(days=days)]
for s in sorted(items, key=lambda x: x["dt"]):
left = (s["dt"] - now).days
label = {0: "今天", 1: "明天"}.get(left, f"{left}天后")
print(f"{s['time']} - {s['event']} ({label})")
r = ScheduleReminder()
r.add("2024-01-15", "09:00", "团队会议"); r.add("2024-01-16", "10:00", "客户拜访")
r.upcoming(7)例子3:密码生成器(random + string)
import random, string
def generate(length=12, upper=True, digits=True, special=True):
chars = string.ascii_lowercase
chars += string.ascii_uppercase if upper else ""
chars += string.digits if digits else ""
chars += "!@#$%^&*" if special else ""
pool = [random.choice(string.ascii_uppercase if upper else chars),
random.choice(string.digits), random.choice(string.ascii_lowercase)]
pool += random.choices(chars, k=length - len(pool))
random.shuffle(pool)
return "".join(pool)
print(generate(16)) # 如需真正安全的随机,改用 secrets.choice常见陷阱
- 日期字符串不能直接相减(
TypeError)→ 先strptime转成datetime再相减。 datetime.now()是本地无时区时间,跨时区比较会出错 → 用datetime.now(timezone.utc)或显式tzinfo。- 随机数每次运行结果不同、难复现 →
random.seed(42);但安全场景不要用固定种子,改secrets。 - 硬编码
"data\\files\\a.txt"只在 Windows 可用 →os.path.join(...)或Path("data")/"files"/"a.txt"。 Counter访问不存在的 key 返回 0 不报错,易误判 → 先用if key in counter判断。defaultdict只读访问缺失 key 也会写入默认值、使字典变大 → 只读场景用.get()或转dict()。
要点
- 路径优先
pathlib:Path("a")/"b.txt"拼接,read_text/write_text读写,rglob递归查找。 Counter计数、defaultdict分组、deque做队列(两端 O(1))、namedtuple替代纯元组。functools.lru_cache一句话优化递归;partial固定参数;itertools处理排列组合与分组。- 日期:
strftime格式化、strptime解析、timedelta计算、时区用timezone.utc。 - 随机数用
seed保证可复现,密码类场景换secrets。 - 文件操作按层次选型:
pathlib(路径)→shutil(复制/移动/删除/打包)→glob(查找)→tempfile(临时文件)。
正则表达式
正则用特殊语法描述字符串模式,用于验证、提取、替换、切分文本。Python 通过 re 模块提供,模式一律写成原始字符串 r"..."(r"\d+" 中的 \d 不会被 Python 转义;写 "\\d+" 才等价)。
import re
re.findall(r"\d+\.?\d*", "订单号:12345,金额:99.8元") # ['12345', '99.8']元字符速查表
| 元字符 | 含义 | 示例 |
|---|---|---|
. |
任意字符(默认不含 \n,re.S 下含) |
c.t → cat/cut/cot |
\d / \D |
数字 / 非数字 | \d+ → 123 |
\w / \W |
单词字符([a-zA-Z0-9_] 及 Unicode 字母、汉字) / 非单词字符 |
\w+ |
\s / \S |
空白符(空格、\t、\n) / 非空白 | \s+ |
^ / $ |
字符串开头 / 结尾(re.M 下为每行首尾) |
^hello, world$ |
\b / \B |
单词边界 / 非单词边界 | \bcat\b 匹配 cat 但不匹配 scatter |
[abc] / [^abc] |
字符集合 / 取反集合 | [a-z0-9_] |
| |
或 | cat|dog |
\ |
转义特殊字符 | 3\.14 只匹配 3.14 |
re.findall(r".at", "cat, bat, hat, mat") # ['cat','bat','hat','mat'] . 占位
re.findall(r"\d+", "我今年25岁,身高175cm") # ['25','175']
re.findall(r"\D+", "abc123def456") # ['abc','def']
re.findall(r"\w+", "hello_world123 你好") # ['hello_world123','你好'] \w 含汉字
re.findall(r"\W+", "hello, world!") # [', ','!']
re.findall(r"\s+", "hello\tworld\n") # ['\t','\n']
re.findall(r"\S+", "hello world") # ['hello','world']字符类
re.findall(r"[abc]", "abcd") # ['a','b','c'] 匹配集合中任一字符
re.findall(r"[a-z]+", "Hello World") # ['ello','orld'] 范围
re.findall(r"[a-zA-Z0-9_]+", "Ab_9 !") # ['Ab_9'] 等价于 \w
re.findall(r"[^0-9]+", "abc123def") # ['abc','def'] ^ 在 [] 内表示取反
re.findall(r"[\d-]+", "电话 138-1234-5678") # ['138-1234-5678'] 集合内可放 \d
re.findall(r"[a-zA-Z0-9._%-]+@", "邮箱 test@example.com") # ['test@'] 取 @ 前的本地部分量词
| 量词 | 含义 |
|---|---|
* |
0 次或多次(\d* 会匹配空串) |
+ |
1 次或多次 |
? |
0 次或 1 次(可选项,如 colou?r) |
{n} / {n,} / {n,m} |
恰好 n 次 / 至少 n 次 / n 到 m 次 |
贪婪与非贪婪:量词默认贪婪(尽可能长),加 ? 变非贪婪(尽可能短)。
re.findall(r"<.*>", "<b>a</b><i>c</i>") # ['<b>a</b><i>c</i>'] 贪婪,一整段
re.findall(r"<.*?>", "<b>a</b><i>c</i>") # ['<b>', '</b>', '<i>', '</i>']
re.findall(r"\d{2,4}", "1 12 1234 12345") # ['12', '1234', '1234']锚点与边界
re.search(r"^hello", "hello world") # 匹配开头
re.search(r"world$", "hello world") # 匹配结尾
re.findall(r"^line", "line1\nline2") # ['line'] 默认只算整个串
re.findall(r"^line", "line1\nline2", re.M) # ['line','line'] 多行模式
re.findall(r"\bcat\b", "cat scatter category") # ['cat'] 仅完整单词
re.findall(r"cat", "cat scatter category") # ['cat','cat','cat']
re.findall(r"\Bcat", "cat scatter") # ['cat'] 非边界,即 scatter 中的 cat分组与捕获
m = re.search(r"(\d{4})-(\d{2})-(\d{2})", "2024-01-15")
m.group(0) # '2024-01-15' 完整匹配
m.group(1), m.groups() # '2024' / ('2024','01','15')
m = re.search(r"姓名:(?P<name>\w+),年龄:(?P<age>\d+)", "姓名:张三,年龄:25")
m.group("name"), m.groupdict() # '张三' / {'name':'张三','age':'25'}
re.findall(r"(\w+) \1", "hello hello world world") # ['hello','world'] 反向引用
re.findall(r"(?:https?)://([\w.]+)", "http://a.com https://b.com") # ['a.com','b.com'] 非捕获- 捕获分组
(...):可group(n)取值;findall有分组时只返回分组内容。 - 命名分组
(?P<name>...):可读性更好,用groupdict()。 - 反向引用
\1/(?P=name):匹配与前面分组相同的文本(查重复词)。 - 非捕获分组
(?:...):只分组不捕获,减少干扰。
预查(零宽断言)
不消耗字符,只要求前后满足条件。
re.findall(r"\d+(?=元)", "价格99元") # ['99'] 正向预查:后面是"元"
re.findall(r"\d+(?!元)", "99元 88美元") # ['88'] 负向预查:后面不是"元"
re.findall(r"(?<=¥)\d+", "¥199") # ['199'] 正向后顾:前面是"¥"(定长)
re.findall(r"(?<!¥)\d+", "¥199 200") # ['200'] 负向后顾re 模块函数
re.match(r"hello", "hello world") # 只从开头匹配,验证格式常用;失败返回 None
re.search(r"world", "hello world") # 扫描全文,返回第一个 match;m.start()/m.end() 取位置
re.findall(r"\d+", "99元,199元") # ['99','199'] 全部匹配的字符串列表
re.findall(r"(\w+):(\d+)岁", "张三:25岁,李四:30岁") # [('张三','25'),('李四','30')] 有分组返回元组
for m in re.finditer(r"(\w+) (\d+)", "apple 10, banana 20"): # 迭代 Match 对象,省内存
print(m.group(1), m.group(2))
re.sub(r"\d{4}", "YYYY", "今天是2024-01-15") # '今天是YYYY-01-15'
re.sub(r"(\d{3})-\d{4}-(\d{4})", r"\1-****-\2", "138-1234-5678") # 反向引用替换
re.sub(r"\d+", lambda m: str(int(m.group()) * 2), "10, 20") # 函数替换 → '20, 40'
re.subn(r"apple", "orange", text) # → (新字符串, 替换次数)
re.split(r"[,;|]", "a,b;c|d") # ['a','b','c','d']
re.split(r"(\d)", "a1b2") # ['a','1','b','2',''] 括号保留分隔符
re.split(r",", "a,b,c,d", maxsplit=2) # ['a','b','c,d']
pattern = re.compile(r"\d+", re.I) # 重复使用时先编译,也便于加标志
pattern.findall(text1); pattern.findall(text2)标志 flags
| 标志 | 简写 | 作用 |
|---|---|---|
re.IGNORECASE |
re.I |
忽略大小写 |
re.MULTILINE |
re.M |
^/$ 匹配每行首尾 |
re.DOTALL |
re.S |
. 也匹配换行符 |
re.VERBOSE |
re.X |
忽略空白并支持 # 注释 |
re.findall(r"^line\d", "line1\nline2", re.M) # ['line1','line2']
re.findall(r"hello.world", "hello\nworld", re.S) # ['hello\nworld']
re.compile(r"hello", re.I | re.M) # 多个标志用 | 组合
phone_re = re.compile(r"""
\d{3} # 区号
- # 分隔符
\d{8} # 号码
""", re.VERBOSE) # 复杂正则加注释常用模式示例
| 目标 | 模式 |
|---|---|
| 邮箱 | ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ |
| 手机号(中国) | ^1[3-9]\d{9}$ |
日期 YYYY-MM-DD |
^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$ |
时间 HH:MM:SS |
^([01]\d|2[0-3]):[0-5]\d:[0-5]\d$ |
| URL | https?://[^\s<>"']+ |
| IPv4 | ^((25[0-5]|2[0-4]\d|1?\d?\d)\.){3}(25[0-5]|2[0-4]\d|1?\d?\d)$ |
| 身份证(18 位) | ^\d{17}[\dXx]$ |
| 邮政编码 | ^[1-9]\d{5}$ |
| 中文字符 | [\u4e00-\u9fff]+ |
| 用户名(字母开头 3-16 位) | ^[a-zA-Z][a-zA-Z0-9_]{2,15}$ |
| 强密码(含大小写+数字+特殊,≥8) | ^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*]).{8,}$ |
| HTML 标签 | <[^>]+> |
链接 href |
<a\s+(?:[^>]*?\s+)?href="([^"]*)"[^>]*>(.*?)</a> |
| 数字(含小数、负数) | -?\d+\.?\d* |
| 重复单词 | (\w+) \1 |
# 多格式日期提取与归一
for d in re.findall(r"\d{4}[-/年]\d{1,2}[-/月]\d{1,2}", "2024-01-15 / 2024/1/5 / 2024年3月8日"):
y, m, dd = re.split(r"[-/年月日]", d)[:3]
print(f"{int(y):04d}-{int(m):02d}-{int(dd):02d}")
re.findall(r"\b\d{1,3}(?:\.\d{1,3}){3}\b", "网关 192.168.1.1 和 8.8.8.8") # IP(粗筛)
re.findall(r"[\u4e00-\u9fff]+", "Hello你好World世界123") # ['你好','世界']
re.sub(r"[\u4e00-\u9fff]+", "", "Hello你好") # 'Hello'实用例子
例子1:手机号 / 邮箱验证与脱敏
import re
def valid_phone(p):
return re.match(r"^1[3-9]\d{9}$", p) is not None
def valid_email(e):
return re.match(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$", e) is not None
def mask_phone(text): # 138****5678
return re.sub(r"1[3-9]\d{9}", lambda m: f"{m.group()[:3]}****{m.group()[7:]}", text)
for p in ["13812345678", "12345678901"]:
print(p, "有效" if valid_phone(p) else "无效")
print(mask_phone("联系方式:13812345678,备用:13987654321"))例子2:日志解析器(命名分组 + Counter)
import re
from collections import Counter
LOG = re.compile(r"\[(?P<time>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\] \[(?P<level>\w+)\] (?P<msg>.+)")
def parse(lines):
logs = [m.groupdict() for line in lines if (m := LOG.search(line))]
stats = Counter(x["level"] for x in logs)
errors = [f"{x['time']} {x['msg']}" for x in logs if x["level"] == "ERROR"]
return logs, stats, errors
sample = """[2024-01-15 10:30:00] [INFO] Application started
[2024-01-15 10:30:05] [ERROR] Database connection failed
[2024-01-15 10:30:20] [ERROR] Query timeout""".split("\n")
logs, stats, errors = parse(sample)
print(stats) # Counter({'ERROR': 2, 'INFO': 1})
print(errors)例子3:数据清洗
import re
def remove_html(html):
return re.sub(r"\s+", " ", re.sub(r"<[^>]+>", "", html)).strip()
def normalize_phone(phone): # 统一成 138-1234-5678
d = re.sub(r"\D", "", phone)
return f"{d[:3]}-{d[3:7]}-{d[7:]}" if len(d) == 11 else phone
def extract_prices(text): # ¥99.99 / 199.50元 / $29.99
pat = r"[¥$]\s*(\d+(?:\.\d{2})?)|(\d+(?:\.\d{2})?)\s*元"
return [float(a or b) for a, b in re.findall(pat, text)]
def clean_emails(text):
return list(set(re.findall(r"\b[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\b", text)))
print(remove_html("<h1>标题</h1><p>正文<b>粗体</b></p>")) # 标题 正文粗体
print(normalize_phone("(138)1234-5678")) # 138-1234-5678
print(extract_prices("A: ¥99.99 B: 199.50元")) # [99.99, 199.5]常见陷阱
- 忘记转义
.、*、+、?、(、)等特殊字符,3.14会匹配3a14→ 写成3\.14。 - 贪婪量词一次吃掉整段(
<div>.*</div>匹配跨标签) → 改非贪婪<div>.*?</div>或用<[^>]+>。 - 不用原始字符串,
"\d+"里的\d被 Python 转义 → 一律r"\d+"。 findall带分组时只返回分组内容,拿不到完整匹配 → 用(?:...)非捕获或改finditer+group(0)。re.match只从头匹配,"world"在"hello world"中匹配失败 → 查找用search。- 在循环里反复传字符串模式会重复编译 → 先
re.compile;正则来自用户输入时先re.escape()。
要点
- 模式一律写
r"...";重复使用的正则先re.compile(顺带加re.I/M/S/X)。 - 验证用
match+^...$锚定,查找用search,全量提取用findall/finditer,替换用sub(支持\1反向引用和函数)。 - 量词默认贪婪,嵌套/标签类匹配一律加
?变非贪婪。 - 提取字段用命名分组
(?P<name>...)+groupdict();不需要捕获时用(?:...)。 - 复杂条件用预查:
(?=...)正向、(?!...)负向、(?<=...)/(?<!...)后顾(定长)。 - 复杂正则用
re.VERBOSE分行加注释;上线前先用真实样例测过再使用。
JSON和CSV处理
JSON:结构化、可嵌套、跨语言的交换格式(dict/list 与字符串互转),API 主流。CSV:逗号分隔的表格数据,Excel 可直接打开。
JSON 处理
基本操作
import json
data = {"name": "张三", "age": 25, "skills": ["Python", "Java"],
"is_student": True, "score": 85.5, "address": None}
json_str = json.dumps(data) # 对象 → JSON 字符串(中文被转义成 \uXXXX)
json_str = json.dumps(data, ensure_ascii=False, indent=2) # 保留中文 + 缩进
# {
# "name": "张三",
# "age": 25,
# ...
# }
obj = json.loads(json_str) # JSON 字符串 → 对象
print(obj["name"], type(obj)) # 张三 <class 'dict'>常用参数:ensure_ascii=False(保留中文)、indent、sort_keys、separators。
类型映射
| Python | JSON | JSON | Python |
|---|---|---|---|
dict |
object {} |
object | dict |
list / tuple |
array [] |
array | list(tuple 读回来变 list) |
str |
string | string | str |
int / float |
number | number | int / float |
True / False |
true / false |
true / false |
True / False |
None |
null |
null |
None |
datetime、集合、自定义对象不在表内,直接 dumps 抛 TypeError。
文件读写
data = {"users": [{"name": "张三", "age": 25}, {"name": "李四", "age": 30}], "count": 2}
with open("data.json", "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2) # 序列化并写入
with open("data.json", "r", encoding="utf-8") as f:
loaded = json.load(f) # 读取并反序列化dump/load 操作文件对象,dumps/loads 操作字符串;内容非法时 load 抛 json.JSONDecodeError。
自定义对象序列化
from datetime import datetime
# 方式1:自定义 JSONEncoder
class DateTimeEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime):
return obj.strftime("%Y-%m-%d %H:%M:%S")
return super().default(obj) # 其他类型交给父类,抛 TypeError
json.dumps({"event": "会议", "time": datetime.now()}, cls=DateTimeEncoder, ensure_ascii=False)
# {"event": "会议", "time": "2024-01-15 14:30:45"}
# 方式2:to_dict / from_dict,可双向转换,最常用
class Student:
def __init__(self, name, age, courses):
self.name, self.age, self.courses = name, age, courses
def to_dict(self):
return {"name": self.name, "age": self.age, "courses": self.courses}
@classmethod
def from_dict(cls, data):
return cls(data["name"], data["age"], data["courses"])
json_str = json.dumps(Student("张三", 18, ["Math"]).to_dict(), ensure_ascii=False)
print(Student.from_dict(json.loads(json_str)).name) # 张三CSV 处理
读取
import csv
# csv.reader:按索引取列
with open("students.csv", "r", encoding="utf-8") as f:
reader = csv.reader(f)
headers = next(reader) # 取出表头 ['姓名', '年龄', '城市']
for row in reader: # row 是 list
print(row[0], row[1])
# csv.DictReader(推荐):按列名取
with open("students.csv", "r", encoding="utf-8") as f:
for row in csv.DictReader(f):
print(row["姓名"], row["年龄"]) # 值全是字符串写入
# csv.writer
data = [["姓名", "年龄", "城市"], ["张三", 25, "北京"], ["李四", 30, "上海"]]
with open("output.csv", "w", newline="", encoding="utf-8") as f: # newline="" 必须有
writer = csv.writer(f)
writer.writerows(data) # 或循环 writer.writerow(row)
# csv.DictWriter(推荐)
students = [{"name": "张三", "age": 25, "city": "北京"},
{"name": "李四", "age": 30, "city": "上海"}]
with open("output.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=["name", "age", "city"])
writer.writeheader() # 写表头
writer.writerows(students)- 读出的值全是
str,计算前需int()/float();字段含分隔符、换行、引号时csv自动加引号(quotechar='"') DictWriter缺字段会抛ValueError,可设restval=""、extrasaction="ignore";quoting=csv.QUOTE_ALL可强制加引号
分隔符与引号
csv.reader(f, delimiter="\t") # TSV:Tab 分隔
csv.reader(f, quotechar="'") # 自定义引号字符(默认 ")实用例子
例子1:JSON 与 CSV 互转
def json_to_csv(json_file, csv_file):
with open(json_file, "r", encoding="utf-8") as f:
data = json.load(f)
if not data:
return
with open(csv_file, "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=data[0].keys()) # 首条记录的键做表头
writer.writeheader()
writer.writerows(data)
def csv_to_json(csv_file, json_file):
with open(csv_file, "r", encoding="utf-8") as f:
data = list(csv.DictReader(f))
with open(json_file, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)例子2:JSON 配置管理器
from pathlib import Path
class ConfigManager:
"""配置不存在时用默认值,set 后立即落盘"""
def __init__(self, config_file="config.json"):
self.config_file = Path(config_file)
self.config = json.load(open(config_file, encoding="utf-8")) \
if self.config_file.exists() else self._default_config()
def _default_config(self):
return {"theme": "light", "language": "zh-CN", "auto_save": True, "max_history": 50}
def save(self):
with open(self.config_file, "w", encoding="utf-8") as f:
json.dump(self.config, f, ensure_ascii=False, indent=2)
def get(self, key, default=None):
return self.config.get(key, default)
def set(self, key, value):
self.config[key] = value
self.save() # 修改后立即落盘
# ConfigManager().set("theme", "dark")常见陷阱
- 中文变成
\u5f20\u4e09:dumps默认ensure_ascii=True转义非 ASCII →ensure_ascii=False,写文件同时encoding="utf-8" - CSV 在 Windows 上每行间多空行:漏传
newline="",csv 写的\r\n被文本模式又转一次 →open(..., "w", newline="", encoding="utf-8") TypeError: Object of type datetime is not JSON serializable:JSON 只支持表中类型 →isoformat()、list()转换,或用cls=JSONEncoder/to_dict()- CSV 值全是字符串、Excel 打开中文乱码:数值需
int()/float();乱码用encoding="utf-8-sig"(带 BOM) - 大 JSON 一次性
json.load()爆内存、手写split(",")解析 CSV:前者改 JSON Lines 逐行json.loads(line),后者一律用csv模块
最佳实践
# 1. 指定编码;JSON 保留中文,CSV 写入必须 newline=""
with open("data.json", "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
with open("data.csv", "w", newline="", encoding="utf-8") as f:
csv.DictWriter(f, fieldnames=fields).writerows(rows)
# 2. 大文件逐行处理(JSON Lines),别一次性 json.load()
with open("large.json", "r", encoding="utf-8") as f:
for line in f:
process(json.loads(line))要点
dumps/loads处理字符串,dump/load处理文件对象;中文要ensure_ascii=False+encoding="utf-8"- 类型映射:dict↔object、list/tuple↔array、True/False/None↔true/false/null;
datetime、tuple、自定义对象需转换或用JSONEncoder/to_dict() - CSV 写入必须
newline=""(否则 Windows 出现空行);读出的值全是字符串,需手动转类型 - 优先
DictReader/DictWriter:表头用next(reader)读、writeheader()写,fieldnames决定列顺序 - 分隔符用
delimiter(\t读 TSV);含分隔符的字段由csv自动加引号,别手写split(",") - 大文件逐行处理;捕获
FileNotFoundError、json.JSONDecodeError、UnicodeDecodeError