目录257

Python基础

第一个Python程序

交互式模式 vs 脚本模式

Python5 行
>>> print("你好")      # 交互式:输入一行,立即执行一行
你好
>>> 1 + 1
2
>>> exit()             # 退出
  • 交互式:命令行输入 python(macOS/Linux 用 python3)进入 >>>;适合试算、验证语法,代码不保存。
  • 脚本模式:代码写入 hello.py,用 python hello.py 运行;代码可保存、修改、分享,是正式写法。
Python3 行
# hello.py
print("Hello, World!")
print("你好,世界!")

基本用法

Python7 行
print("Hello")                 # 输出:Hello
print(123)                     # 输出:123
print("我", "爱", "Python")     # 输出:我 爱 Python(多值默认空格分隔)
print(1 + 2 + 3)               # 输出:6(先求值再打印)

name, age = "小红", 18
print("我叫", name)             # 输出:我叫 小红

文本必须加引号(单双引号等价),数字不用。

sep 与 end

Python10 行
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(".")   # 输出:加载中..

格式化输出

Python8 行
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 格式规范

{值:[[填充]对齐][宽度][,][.精度][类型]}

Python10 行
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() → '苹果'

注释

Python13 行
# 单行注释:# 之后的内容被忽略
age = 18  # 也可以在代码后面注释

"""
三引号包裹的是多行字符串,常作模块/函数/类的文档字符串(docstring)
"""

def calculate(radius):
    """计算圆面积:π × r²(函数文档)"""
    return 3.14159 * radius ** 2

# TODO: 待实现的功能
# FIXME: 已知问题

注释写“为什么这么做”和注意事项,不写“这行代码在干什么”;代码本身应足够清晰。

input() 函数

Python9 行
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() 转字符串。

综合示例:

Python9 行
print("=== 自我介绍小程序 ===")
name = input("你叫什么名字?")
age = int(input("你几岁了?"))          # 需要参与比较/运算时立刻转换
city = input("你住在哪个城市?")

print(f"\n姓名:{name}")
print(f"年龄:{age}岁")
print(f"城市:{city}")
print(f"\n{name},你好!欢迎来自{city}的{age}岁的你!")

转义字符

Python6 行
print("第一行\n第二行")        # \n 换行
print("姓名\t年龄\t城市")      # \t 制表符,用于对齐
print("他说:\"你好!\"")      # \" 在双引号串内输出引号
print('She said: "Hello!"')   # 或直接用单引号包裹
print("C:\\Users\\Desktop")   # \\ 表示一个反斜杠
print(r"C:\Users\Desktop")    # r 前缀:原始字符串,不转义

代码风格(PEP 8)

Python10 行
# 推荐
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、常量全大写,名字要能自解释。

基础语法

变量

命名规则

Python7 行
user_name = "张三"     # 只能含字母、数字、下划线
age2 = 20             # 可以含数字,但不能以数字开头
_private = "私有"      # 可以下划线开头(约定表示内部使用)

# 2age = 20           # 非法:以数字开头
# user-name = "张三"   # 非法:连字符会被当成减号
# for = 10            # 非法:关键字不能作变量名
  • 只能包含字母、数字、下划线;不能以数字开头;区分大小写(name 与 Name 是两个变量)。
  • 不能用关键字命名;风格上用 snake_case,常量全大写,名字要能自解释。

赋值与解包

Python8 行
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

动态类型

Python4 行
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} 花括号无冒号,自动去重,支持交并差
Python8 行
name = "小明"                     # 双引号
message = '你好'                  # 单引号,与双引号等价
multi = """这是
多行字符串"""
fruits = ["苹果", "香蕉", "橙子"]
point = (10, 20)                  # 坐标点
person = {"name": "张三", "age": 25}
unique = {1, 2, 3, 3}             # {1, 2, 3}:自动去重
Python6 行
age = input("年龄:")
if age.isdigit():          # 先检查是否为纯数字字符串,再转换
    age = int(age)
    print(f"你的年龄是{age}岁")
else:
    print("请输入数字!")

类型转换

Python7 行
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:

Python15 行
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}!")

运算符

算术运算符

Python7 行
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:幂运算

比较运算符

Python4 行
5 == 5     # True:== 是比较,= 是赋值,不要混用
5 != 3     # True
5 > 3      # True
5 >= 5     # True

逻辑运算符

Python10 行
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("良好")

赋值运算符

Python10 行
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 用缩进(而非 {})表示代码块,同一缩进级别的代码属于同一个块。

Python8 行
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。

标识符与关键字

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

多行语句与同行多语句

Python11 行
# 括号内可直接换行(推荐)
total = (1 + 2 + 3 + 4 +
         5 + 6 + 7 + 8)
fruits = ["苹果", "香蕉",
          "橙子", "葡萄"]

# 反斜杠续行:\ 之后不能有任何字符(包括空格和注释)
total = 1 + 2 + \
        3 + 4

x = 10; y = 20        # 分号可在一行写多条语句,但不推荐,可读性差

实用例子

例子1:交换变量

Python3 行
a, b = 100, 200
a, b = b, a          # 一行完成交换,无需临时变量
print(a, b)          # 输出:200 100

例子2:输入校验

Python5 行
try:
    age = int(input("请输入年龄:"))
    print(f"你的年龄是{age}岁")
except ValueError:        # 输入非数字时 int() 抛 ValueError
    print("请输入有效的数字!")

例子3:解包赋值

Python8 行
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

Python5 行
age = 18
temperature = -10

big = 123456789012345678901234567890   # Python 的 int 无大小限制,不会溢出
huge = 10 ** 100                       # 1 后面 100 个 0

进制与转换

Python15 行
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

Python3 行
price = 99.9
big = 1.5e3      # 1500.0:e 表示 10 的幂
small = 1.5e-3   # 0.0015

精度问题

十进制小数大多无法用二进制精确表示,因此浮点运算存在误差:

Python2 行
print(0.1 + 0.2)          # 0.30000000000000004
print(0.1 + 0.2 == 0.3)   # False:不要用 == 比较浮点数

三种处理方式:

Python5 行
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

Python4 行
c = 3 + 4j          # Python 用 j 表示虚数单位
c.real              # 3.0
c.imag              # 4.0
(1 + 2j) * (3 + 4j) # (-5+10j)

算术运算符

Python12 行
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;不确定时一律加括号

常见用法:

Python10 行
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 + 利率)^年数

数学函数

内置函数

Python10 行
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 模块

Python16 行
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 模块

Python10 行
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) 设定随机种子,使结果可复现

数字类型转换

Python8 行
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:贷款月供(等额本息)

Python12 行
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:单位转换工具

Python12 行
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:掷骰子

Python12 行
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 生成随机数与抽样。

字符串详解

创建字符串

单引号和双引号

单双引号完全等价,选哪种取决于字符串内部含哪种引号,避免转义。

Python4 行
name = 'Python'
message = "Hello, World!"
text1 = "I'm a student"      # 内含单引号 → 用双引号包
text2 = 'He said "Hello"'    # 内含双引号 → 用单引号包

三引号(多行字符串)

保留换行与全部格式,常用于 docstring、模板文本。

Python9 行
poem = '''
床前明月光,
疑是地上霜。
'''

doc = """
这是多行字符串
保留所有格式
"""

转义字符

转义 含义
\n 换行
\t 制表符(Tab)
\\ 反斜杠本身
\' \" 引号
\r 回车
\b 退格
\0 空字符
Python3 行
print("第一行\n第二行")
print("姓名\t年龄\t城市")
print("C:\\Users\\Desktop")   # 路径中的反斜杠必须写成 \\

原始字符串

前缀 r/R 使反斜杠不转义,用于 Windows 路径和正则表达式。

Python2 行
path = r"C:\new\test.txt"   # \n 不会被转义成换行
pattern = r"\d+\.\d+"       # 匹配小数

字符串操作

拼接与重复

Python4 行
full_name = "张" + "三"          # 张三
laugh = "哈" * 5                 # 哈哈哈哈哈
# "我今年" + 18 + "岁"  → TypeError
message = "我今年" + str(18) + "岁"   # 非字符串须先 str()

格式化:f-string(推荐)

字符串前加 f,{} 内可直接写变量或表达式。

Python10 行
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()

Python3 行
"我叫{},今年{}岁".format("小红", 18)               # 按位置
"我叫{0},今年{1}岁,{0}很高兴".format("小红", 18)  # 指定索引
"我叫{name},今年{age}岁".format(name="小红", age=18)  # 指定名称

格式化:%(旧写法)

Python1 行
"我叫%s,今年%d岁,考了%.1f分" % (name, age, score)  # %s字符串 %d整数 %f浮点
占位符 含义
%s 字符串(任何对象都可)
%d 十进制整数
%f 浮点数,%.1f 保留 1 位
%x 十六进制整数
%% 字面量 %

只在阅读旧代码时需要,新代码一律用 f-string。

索引和切片

索引

正向从 0 开始,负向从 -1 开始(-1 是最后一个),越界抛 IndexError。

Python3 行
text = "Python"
print(text[0], text[5])    # P n
print(text[-1], text[-6])  # n P

切片

[起始:结束:步长],左闭右开(不含结束位置)。

Python7 行
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(反转)

常用切片技巧

Python5 行
text = "Python Programming"
text[:5]     # Pytho       取前5个
text[-5:]    # mming       取后5个
text[1:-1]   # ython Programmin  去掉首尾字符
text[::-1]   # gnimmargorP nohtyP 反转

字符串方法

所有方法都返回新字符串,不修改原字符串。

大小写转换

Python6 行
text = "Hello World"
text.upper()       # HELLO WORLD
text.lower()       # hello world
text.capitalize()  # Hello world(首字母大写)
text.title()       # Hello World(每个单词首字母大写)
text.swapcase()    # hELLO wORLD(互换)

查找和替换

Python6 行
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个

判断

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

分割和连接

Python4 行
"apple,banana,orange".split(",")   # ['apple', 'banana', 'orange']
"第一行\n第二行".splitlines()      # ['第一行', '第二行'],自动处理 \r\n
",".join(['apple', 'banana'])      # apple,banana
" | ".join(['apple', 'banana'])    # apple | banana

去除空白

Python4 行
"  Hello  ".strip()    # "Hello"(两端)
"  Hello  ".lstrip()   # "Hello  "(左端)
"  Hello  ".rstrip()   # "  Hello"(右端)
"***Hello***".strip("*")  # "Hello"(可指定字符)

对齐

Python5 行
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

字符串不可变

创建后不能修改单个字符,任何“修改”都是生成新字符串(因此可安全用作字典键)。

Python4 行
text = "Hello"
# text[0] = "h"  # TypeError
text = "h" + text[1:]        # hello
text = text.replace("H", "h")

实用例子

密码强度验证

Python12 行
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("密码强度:强")

格式化输出表格

Python7 行
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}")

敏感词过滤

Python4 行
text = "这是一条包含敏感词的评论"
for word in ["敏感词", "不良信息", "违规"]:
    text = text.replace(word, "*" * len(word))
print(text)  # 这是一条包含***的评论

提取文件名与扩展名

Python5 行
filename = "document.pdf"
name, ext = filename.rsplit(".", 1)   # rsplit 从右侧分割,适应多圆点文件名

dot = filename.rfind(".")             # 等价写法
name, ext = filename[:dot], filename[dot+1:]

简单文本分析

Python6 行
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()。
  • find vs index:不确定子串是否存在时用 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)是有序、可变、可重复的元素序列,元素可以是任意类型。

Python3 行
empty_list = []
numbers = [1, 2, 3, 4, 5]
mixed = [1, "hello", 3.14, True, [1, 2, 3]]   # 支持混合类型

创建列表

Python4 行
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 更简洁高效。

Python4 行
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。

Python3 行
fruits = ["苹果", "香蕉", "橙子", "葡萄"]
print(fruits[0], fruits[1])    # 苹果 香蕉
print(fruits[-1], fruits[-2])  # 葡萄 橙子

切片

[起始:结束:步长],左闭右开,返回新列表(不修改原列表)。

Python8 行
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] 反转

修改列表

列表可变,这是与字符串最大的区别。

Python6 行
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],列表随之缩短

添加元素

Python5 行
fruits = ["苹果", "香蕉"]
fruits.append("橙子")        # 末尾追加一个元素
fruits.insert(1, "西瓜")     # 在索引1插入,其后元素后移;索引越界则插到末尾
fruits.extend(["葡萄", "梨"]) # 追加可迭代对象中的所有元素
fruits = fruits + ["芒果"]   # + 生成新列表,原列表不变

extend() 原地修改(更高效);+ 生成新列表(保留原列表时用)。

删除元素

Python6 行
fruits = ["苹果", "香蕉", "橙子", "葡萄"]
del fruits[1]            # 按索引删除;del fruits[1:3] 删除切片
fruits.remove("香蕉")    # 按值删除第一个匹配项,不存在抛 ValueError
last = fruits.pop()      # 删除并返回末尾元素
first = fruits.pop(0)    # 删除并返回指定索引元素
fruits.clear()           # 清空内容,列表对象保留

用 remove() 前先用 in 判断存在性可避免异常。

列表操作

查找

Python4 行
fruits = ["苹果", "香蕉", "橙子", "香蕉"]
fruits.index("香蕉")   # 1,第一次出现的位置,不存在抛 ValueError
fruits.count("香蕉")   # 2
"苹果" in fruits       # True

排序

Python8 行
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 降序。

反转

Python3 行
fruits = ["苹果", "香蕉", "橙子"]
fruits.reverse()          # 原地反转,返回 None
reversed_fruits = fruits[::-1]  # 返回新列表,原列表不变

其他操作

Python7 行
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 深拷贝

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

遍历列表

Python13 行
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}")

嵌套列表(二维列表)

Python11 行
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

实用例子

成绩统计

Python11 行
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)}")

去重(两种方法)

Python8 行
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)

购物车(列表 + 字典配合)

Python15 行
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)

Python22 行
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

找出重复元素

Python7 行
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() 会跳元素,应改用列表推导式重建、遍历副本或倒序删除。
Python11 行
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)是不可变的有序序列,用于存放确定后不应改变的数据(坐标、配置、日期等);因此防误改、更省内存、访问更快、可作字典键。

创建元组

Python10 行
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,切片左闭右开),只是结果仍是元组。

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

元组不可变

Python4 行
point = (10, 20)
# point[0] = 15      # TypeError: 不支持元素赋值
# point.append(30)   # AttributeError: 无此方法
# del point[0]       # TypeError: 不能删除元素

元组本身不可变,但其中的可变对象内容可以改变:

Python3 行
t = ([1, 2], [3, 4])
# t[0] = [5, 6]   # TypeError:不能替换元素
t[0].append(3)    # ([1, 2, 3], [3, 4]),列表内部被修改

元组解包

Python12 行
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 生成可用属性名访问的元组子类,兼具元组特性与可读性。

Python6 行
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 不可以 可以
Python2 行
point = (10, 20)          # 坐标、RGB颜色、日期等固定数据
locations = {(0, 0): "原点", (10, 20): "点A"}   # 作字典的键(列表不行)

实用例子

坐标距离计算

Python11 行
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}")

数据库查询结果

Python7 行
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}")

颜色混合

Python4 行
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),与数据量无关。

Python3 行
student_list = ["张三", 18, "男", 95]     # 列表:靠位置,不直观
student_dict = {"name": "张三", "age": 18, "gender": "男", "score": 95}
print(student_dict["name"])               # 字典:靠有意义的名字访问

字典的特点

  1. 键值对存储:{"name": "张三"} 中 "name" 是 key,"张三" 是 value。
  2. key 必须唯一:重复 key 后者覆盖前者,{"name": "张三", "name": "李四"} 得 {'name': '李四'}。
  3. key 必须是不可变(可哈希)类型:字符串、数字、元组、布尔可以;列表、字典、集合不行(TypeError: unhashable type)。
  4. value 可以是任意类型:数字、字符串、列表、嵌套字典、布尔等。
  5. 有序(Python 3.7+):保持插入顺序;3.6 及以前不保证。
Python2 行
d = {"string": "字符串key", 123: "数字key", (1, 2): "元组key", True: "布尔key"}
# d = {[1, 2]: "x"}  # TypeError:列表不可哈希

创建字典

Python6 行
empty = {}            # 空字典,或 dict()
person = {"name": "李四", "age": 25, "city": "北京"}

dict([("name", "王五"), ("age", 30)])      # 从键值对序列
dict(name="赵六", age=22, city="上海")      # 关键字参数
dict(zip(["name", "age"], ["孙七", 28]))   # 两个列表 zip 组合

访问元素

Python5 行
student = {"name": "小明", "age": 18, "score": 95}

student["name"]                # 小明;key 不存在抛 KeyError
student.get("gender")          # None,不报错
student.get("gender", "未知")   # 未知,可指定默认值

优先用 get(),避免 KeyError。

修改与删除

Python8 行
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()              # 清空

字典操作

Python6 行
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)]

合并字典

Python5 行
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+)

复制字典

Python7 行
original = {"a": 1}
copy1 = original.copy()        # 浅拷贝
copy2 = dict(original)         # 浅拷贝

import copy
nested = {"a": {"b": 1}}
deep = copy.deepcopy(nested)   # 深拷贝,嵌套对象也独立

遍历字典

Python8 行
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}")

字典推导式

Python3 行
{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'}

嵌套字典

Python5 行
students = {
    "student1": {"name": "张三", "scores": {"语文": 90, "数学": 95}},
    "student2": {"name": "李四", "scores": {"语文": 88, "数学": 92}}
}
print(students["student1"]["scores"]["数学"])   # 95,逐层取键

defaultdict 与 OrderedDict

Python7 行
from collections import defaultdict

counts = defaultdict(int)     # 不存在的 key 自动取 int() = 0
counts["apple"] += 1          # 不会 KeyError

groups = defaultdict(list)    # 默认值为列表
groups["fruits"].append("apple")
Python3 行
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

实用例子

词频统计

Python9 行
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})

学生成绩管理(嵌套字典)

Python12 行
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}")

商品库存管理

Python13 行
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}"

通讯录

Python6 行
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),主要用于去重和集合运算。

创建集合

Python9 行
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}

增删查

Python12 行
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

集合运算

Python8 行
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。

集合判断

Python8 行
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,无共同元素

集合推导式

Python2 行
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 或集合的元素。

Python4 行
fs = frozenset([1, 2, 3])
# fs.add(4)  # AttributeError

d = {frozenset([1, 2]): "a", frozenset([3, 4]): "b"}

遍历

集合无序,遍历顺序不确定;需要稳定顺序时用 sorted()。

Python4 行
for fruit in fruits:        # 顺序不确定
    print(fruit)
for fruit in sorted(fruits):  # 按排序结果遍历
    print(fruit)

实用例子

去重(含保持顺序)

Python11 行
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

共同好友分析

Python6 行
friends_a = {"张三", "李四", "王五", "赵六"}
friends_b = {"李四", "王五", "孙七", "周八"}

friends_a & friends_b   # {'李四', '王五'}  共同好友
friends_a - friends_b   # {'张三', '赵六'}  A 独有
friends_a | friends_b   # 全部好友

统计唯一单词

Python6 行
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 可选。

Python5 行
age = 18
if age >= 18:
    print("你已成年")
    print("可以投票")
print("程序继续")   # 不在 if 内,总会执行
Python11 行
score = 85
if score >= 90:
    print("优秀")
elif score >= 80:
    print("良好")
elif score >= 70:
    print("中等")
elif score >= 60:
    print("及格")
else:
    print("不及格")

执行顺序:从上到下依次判断,第一个为 True 的分支执行后立即跳出整个结构;全不满足才走 else。

条件表达式

Python25 行
# 比较:== != > < >= <=
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

Python7 行
if age >= 18:
    if has_license:
        print("有驾照" if has_car else "没车")
    else:
        print("没驾照")
else:
    print("未成年")

嵌套超过两层就该用卫语句或逻辑运算摊平。

三元表达式

Python3 行
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。

Python6 行
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+)

Python9 行
match command:
    case "new":
        print("创建新文件")
    case "open":
        print("打开文件")
    case "save" | "saveas":   # 用 | 匹配多个值
        print("保存文件")
    case _:                   # 默认分支
        print("未知命令")

实用例子

判断闰年

Python5 行
year = int(input("输入年份:"))
if (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0):
    print(f"{year}是闰年")
else:
    print(f"{year}不是闰年")

BMI 分级

Python13 行
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("肥胖")

三角形判断(嵌套 + 多条件组合)

Python14 行
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

Python8 行
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)

遍历各种对象

Python24 行
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

Python22 行
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)

嵌套循环

Python10 行
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

跳出所在的整个循环。

Python7 行
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

跳过本次循环剩余语句,直接进入下一轮。

Python4 行
for i in range(10):
    if i % 2 == 0:
        continue
    print(i)    # 1, 3, 5, 7, 9

for-else

循环未被 break 中断(正常走完)时执行 else 块,常用于「查找」场景。

Python7 行
num = 17
for i in range(2, num):
    if num % i == 0:
        print(f"{num}不是质数")
        break
else:
    print(f"{num}是质数")

实用例子

九九乘法表(嵌套循环)

Python4 行
for i in range(1, 10):
    for j in range(1, i + 1):
        print(f"{j}×{i}={i*j}", end="\t")
    print()   # 一行结束换行

成绩统计(遍历字典列表 + 条件累加)

Python21 行
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)

Python15 行
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。

基本形式

Python4 行
count = 0
while count < 5:
    print(f"第{count}次")
    count += 1      # 必须有推进条件的语句,否则死循环

先判断条件再执行;每次执行完重新判断;条件恒为 True 则无限循环。

while vs for

Python17 行
# 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

Python6 行
while True:                      # break 跳出循环
    guess = int(input("猜一个数字:"))
    if guess == secret:
        print("猜对了!")
        break
    print("太小了" if guess < secret else "太大了")
Python6 行
num = 0
while num < 10:                  # continue 跳过本轮剩余语句
    num += 1
    if num % 2 == 0:
        continue                 # 注意:continue 前必须已推进 num,否则死循环
    print(num)   # 1, 3, 5, 7, 9

while-else

条件变为 False 而正常结束(未被 break)时执行 else。

Python9 行
num = 17
i = 2
while i < num:
    if num % i == 0:
        print(f"{num}不是质数")
        break
    i += 1
else:
    print(f"{num}是质数")

无限循环

Python15 行
# 故意:服务器/交互式主循环,靠 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 常见模式

Python21 行
# 模式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

Python8 行
i = 1
while i <= 9:
    j = 1
    while j <= i:
        print(f"{j}×{i}={i*j}", end="\t")
        j += 1
    print()
    i += 1

每层循环都要有自己独立的计数器与推进语句。

实战例子

登录系统(限次 + break)

Python9 行
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 + 分支)

Python30 行
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("无效选择")

最大公约数(辗转相除法)

Python8 行
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 条件]。

Python6 行
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 之后。

Python3 行
# [表达式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]
Python13 行
# 嵌套循环:多个 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 序列}。

Python8 行
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 序列},结果自动去重。

Python3 行
{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 序列),返回惰性生成器,按需产出、几乎不占内存,但只能遍历一次。

Python4 行
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))          # 直接传给函数时可再省一层括号

只遍历一次或数据量巨大时用生成器;需要索引、多次访问或求长度时用列表。

实战例子

数据清洗与筛选

Python6 行
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")]

矩阵操作(嵌套推导式)

Python3 行
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]

成绩统计(列表 + 字典推导式组合)

Python11 行
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 循环。
Python7 行
# 复杂逻辑拆成循环更清晰
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处
可读性 看不出在做什么 函数名即语义
扩展性 加圆要复制代码 加圆只需一行调用

定义与调用

Python9 行
def function_name(parameters):
    """文档字符串(可选)"""
    # 函数体
    return result

def say_hello():
    print("Hello!")

say_hello()  # 输出:Hello!

命名规范:小写+下划线,动词开头(calculate_area、get_user_input),避免 func、data 这类无意义名字。

参数

无参数 / 单个 / 多个

Python8 行
def greet(name):
    print(f"Hello, {name}!")

def add(a, b):
    return a + b

greet("小明")      # 输出:Hello, 小明!
print(add(3, 5))  # 输出:8

默认参数

Python5 行
def greet(name, greeting="你好"):
    print(f"{greeting}, {name}!")

greet("小明")            # 输出:你好, 小明!
greet("小红", "Hello")   # 输出:Hello, 小红!

默认参数必须放在无默认值参数之后:def func(a=1, b) 是 SyntaxError,应写成 def func(b, a=1)。

关键字参数

Python6 行
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)        # 混合时位置参数必须在前

返回值

Python11 行
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

提前返回可简化分支:

Python2 行
def is_even(num):
    return num % 2 == 0   # 等价于 if/else 返回 True/False

文档字符串

Python15 行
def calculate_area(width, height):
    """
    计算矩形面积

    参数:
        width: 宽度
        height: 高度

    返回:
        矩形面积
    """
    return width * height

print(calculate_area.__doc__)
help(calculate_area)

函数调函数与递归

Python19 行
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)应改循环或加缓存。

Python5 行
def factorial_iter(n):      # 等价的循环写法,无深度限制
    result = 1
    for i in range(2, n + 1):
        result *= i
    return result

变量作用域

Python8 行
x = 10          # 全局变量

def my_function():
    y = 10      # 局部变量,函数外不可见
    print(x)    # 可以读取全局变量

my_function()
# print(y)     # NameError: y 不存在

修改全局变量需要 global:

Python9 行
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 计算器(多返回值 + 文档字符串)

Python24 行
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 + 校验分支)

Python18 行
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}")

格式化表格输出(组合用法)

Python17 行
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 收集多余的关键字参数为字典。

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

参数解包

Python11 行
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="北京")

仅限关键字与仅限位置参数

Python11 行
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 只能用位置

函数注解

类型提示仅作说明,运行时不强制检查。

Python5 行
def greet(name: str, age: int) -> str:
    return f"{name}, {age}岁"

print(greet("张三", 25))
print(greet(123, "abc"))  # 仍能运行,但不推荐

lambda 表达式

单行的匿名函数,只能写一个表达式并自动返回结果;复杂逻辑用 def(完整语法与场景见第 16 章)。

闭包

内部函数引用了外部函数的变量,外部函数返回该内部函数,变量被保留下来。

Python9 行
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:

Python10 行
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),带参数的装饰器要多包一层工厂函数。

Python19 行
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 返回迭代器,只能消费一次。

Python8 行
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 预先固定部分参数,生成一个新函数。

Python7 行
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 的,影响调试、日志与文档生成。

实战例子

缓存装饰器(闭包保存状态)

Python17 行
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))  # 无缓存会指数级递归,加缓存后秒出

重试装饰器(带参数的装饰器)

Python21 行
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 基础语法

Python1 行
lambda 参数1, 参数2, ... : 表达式

特点:匿名、只能写一个表达式、自动返回表达式结果、没有文档字符串。

Python9 行
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 参数传递

Python10 行
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)

字典排序

Python4 行
scores = {"张三": 85, "李四": 92, "王五": 78}
# 按 value 降序,重新构造有序字典
sorted_scores = dict(sorted(scores.items(), key=lambda x: x[1], reverse=True))
print(sorted_scores)  # {'李四': 92, '张三': 85, '王五': 78}

立即执行

Python1 行
print((lambda x, y: x * y)(5, 3))  # 15

map() - 映射

对每个元素应用函数,返回迭代器(只能消费一次)。

Python11 行
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() - 过滤

保留使函数返回真的元素。

Python9 行
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。

Python10 行
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],展平一层

组合使用

Python12 行
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 用法一致。

Python4 行
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()

Python7 行
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()

Python4 行
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 组合)

Python14 行
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 规则 + 字段扩展)

Python13 行
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)

Python8 行
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。

导入模块时解释器做四件事:找到文件 → 执行文件里的全部代码 → 建立独立命名空间 → 返回模块对象。

Python7 行
# mymath.py
print("mymath 被导入了")   # 导入时会执行一次

def add(a, b):
    return a + b

PI = 3.14159
Python4 行
# 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__" 可以让一个文件两者兼得。

导入模块的方式

Python7 行
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 path

import 模块名

最推荐:访问要带 模块名. 前缀,来源一目了然,同名函数不会互相覆盖。

Python7 行
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 名字

只导入需要的部分,省掉前缀。

Python6 行
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,前者被顶掉且不报错。用别名区分:

Python2 行
from math import sqrt as math_sqrt
from numpy import sqrt as np_sqrt

from 模块名 import *(慎用)

一次性导入所有不以下划线开头的名字,三个问题:

  1. 命名空间污染——不知道哪些名字被塞进当前作用域
  2. 命名冲突——from math import * 后 from statistics import *,mean() 被后者覆盖且无任何提示
  3. 可读性差——几个月后看不出 sqrt 来自哪个模块

只在交互式环境(Jupyter)临时实验时可接受,正式代码一律显式导入。

别名 as

Python4 行
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。

导入子模块

Python4 行
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 * 本身就不会导出以下划线开头的名字。

Python8 行
# 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 时按以下顺序查找:

  1. 当前目录(入口脚本所在目录)
  2. 环境变量 PYTHONPATH 指定的目录
  3. 标准库目录
  4. site-packages(第三方包安装位置)
Python7 行
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__ 是模块名。

Python2 行
# test.py
print(f"__name__ = {__name__}")
Bash2 行
python test.py            # __name__ = __main__
python -c "import test"   # __name__ = test

把测试/演示代码关进这个块,别人导入时就不会被执行:

Python10 行
# 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 安静无声。它也是命令行工具的入口写法:

Python18 行
# 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()

Python7 行
import math
dir(math)              # 模块中所有名字
dir()                  # 不传参:当前作用域的名字
help(math.sqrt)        # 查看文档
math.sqrt.__doc__      # 直接取文档字符串
math.__file__          # 模块文件路径
math.__name__          # 模块名

模块缓存与 reload

模块只在第一次 import 时执行,之后从 sys.modules 取缓存;运行中改了源码再 import 不会生效。

Python6 行
import importlib, mymodule
importlib.reload(mymodule)        # 强制重跑模块代码

import sys
del sys.modules["mymodule"]       # 或删掉缓存条目后重新 import
import mymodule

reload 只重跑该模块本身:之前 from mymodule import X 出来的名字仍指向旧对象,已创建的实例也不会更新类。交互式调试才用,生产代码不要依赖。

创建自己的模块

一个 .py 文件写几个函数就是模块:

Python7 行
# greeting.py
def say_hello(name):
    """打招呼"""
    return f"你好, {name}!"

def say_goodbye(name):
    return f"再见, {name}!"
Python3 行
# main.py
import greeting
print(greeting.say_hello("张三"))

模块的推荐结构

Python23 行
"""模块文档字符串:用途、提供的功能、作者、版本"""

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() 读取:

Python18 行
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

Python29 行
# 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}")
Python7 行
# 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 模块骨架

读文件失败时返回默认值而不是抛异常,是工具模块常见的“宽容”设计:

Python41 行
# 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
Python6 行
# mypackage/math_utils.py
def add(a, b):
    return a + b

def multiply(a, b):
    return a * b
Python6 行
# mypackage/string_utils.py
def reverse(text):
    return text[::-1]

def capitalize_words(text):
    return text.title()

__init__.py 的作用

  1. 标识这是一个包(Python 3.3+ 有命名空间包,可以没有它,但仍建议保留)
  2. 包的初始化代码:导入包时自动执行,可在这里聚合常用 API
  3. 控制 from package import * 的行为(配合 __all__)
Python7 行
# 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,而不必知道内部模块划分。

使用包的三种写法

Python8 行
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))

绝对导入与相对导入

Python5 行
# mypackage/math_utils.py
from mypackage.string_utils import reverse   # 绝对导入:从包根写全路径,推荐
from .string_utils import reverse            # 相对导入:. 当前包,.. 上一级包
from . import helpers                        # 导入同级模块
from ..config import settings                # 导入上一级的模块
  • 绝对导入:路径写全,清晰、不怕移动文件,推荐在应用代码中使用
  • 相对导入:依赖当前包位置,改包名不用改导入;只能在包内部用,且不能在直接运行的脚本里用
Bash2 行
python mypackage/module.py    # 错误:相对导入在非包上下文中运行会报 ImportError
python -m mypackage.module    # 正确:以模块方式运行

子包

包里可以继续放包,形成层级:

mypackage/
├── __init__.py
├── utils/
│   ├── __init__.py
│   ├── text.py
│   └── number.py
└── core/
    ├── __init__.py
    └── engine.py
Python3 行
from 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
Python5 行
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.0
Python9 行
import 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)        # 原地打乱
Python8 行
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      # 相隔天数
Python8 行
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")            # 环境变量
Python6 行
import sys
sys.argv            # ['script.py', 'arg1', ...] 命令行参数
sys.path            # 模块搜索路径
sys.version_info    # 版本元组 (3, 11, 4, ...)
sys.exit(1)         # 退出程序
sys.modules         # 已加载模块缓存

第三方包与 pip

Bash10 行
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 管理依赖:

Bash2 行
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。

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

Python5 行
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 / +

Python28 行
# '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 读会变 浣犲ソ)。

Python3 行
open("f.txt", encoding="utf-8")   # 国际通用,支持全部语言,首选
open("f.txt", encoding="gbk")     # Windows 中文环境的老文件
open("f.txt", encoding="ascii")   # 仅英文
Python9 行
# 不知道文件编码时依次回退尝试
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__() 关闭文件。

Python9 行
# 等价于 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() — 一次读完

Python7 行
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() — 一次读一行

Python6 行
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() — 全部行读成列表

Python5 行
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:])。

迭代文件对象(推荐)

Python14 行
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
迭代文件对象 低(逐行) 大多数情况(推荐) 简洁高效 无

写入文件

Python12 行
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()

Python5 行
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 常用操作

Python18 行
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+,推荐)

Python21 行
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。

Python7 行
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)

异常处理

常见文件异常

Python13 行
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}")

安全读写封装

Python10 行
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 default

FileNotFoundError、PermissionError、IsADirectoryError 都继承自 OSError(IOError 是其别名)。

实用例子

例子1:解析 key=value 配置文件

Python17 行
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:日志记录器

Python18 行
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:分块复制大文件 + 目录内关键词搜索

Python28 行
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

最佳实践

Python6 行
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 捕获并让程序继续。

Python7 行
result = 10 / 0      # ZeroDivisionError: division by zero,后续代码不会执行

try:
    result = 10 / 0
except ZeroDivisionError:
    result = 0
print("程序继续运行")   # 会执行

不处理异常的代价:程序直接退出、用户看到一堆堆栈、还可能留下不一致状态(例如钱已扣但订单未处理)。异常处理带来四点收益:错误可控、提示友好、出错可回滚、便于记录日志。

try/except 基本语法与执行顺序

Python4 行
try:
    risky_code()            # 1. 先执行 try 块
except SomeError:           # 2. 抛出的异常类型匹配才进入
    handle_error()
  • try 块中没有异常 → 跳过所有 except,继续执行整个结构之后的代码。
  • try 块中一旦抛异常 → 立即中断剩余语句,从上到下依次匹配 except;第一个匹配的分之执行。
  • 没有任何 except 匹配 → 异常继续向外层调用栈传播,最终未被捕获则程序终止。
  • 多个 except 时,子类必须写在父类前面,否则父类先匹配,子类的分支永远不执行。
Python7 行
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 的原因。

捕获多个异常

Python14 行
# 写法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 系) 错误号、相关文件名
Python4 行
try:
    int("二十岁")
except ValueError as e:
    print(f"{type(e).__name__}: {e}")   # ValueError: invalid literal for int()...

try/except/else/finally

完整结构及执行顺序:

Python8 行
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 之前。
Python10 行
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 都会关闭

完整例子(数据库查询,展示执行顺序):

Python12 行
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()               # 总是执行

捕获所有异常

Python4 行
try:
    risky_operation()
except Exception as e:              # 捕获除 SystemExit/KeyboardInterrupt 之外的所有异常
    print(f"发生错误:{type(e).__name__}: {e}")

裸 except: 等价于 except BaseException:,会连 KeyboardInterrupt、SystemExit 一起吞掉,导致程序无法退出且拿不到异常对象,禁止使用。只有在最外层做兜底记录(并随后重新抛出)时才可以考虑 except Exception。

抛出异常:raise

Python4 行
def divide(a, b):
    if b == 0:
        raise ValueError("除数不能为0")     # raise 异常类(参数)
    return a / b

重新抛出

处理完(如记日志)后让上层继续处理,用不带参数的 raise,它会保留原始异常的类型和堆栈:

Python6 行
def process_data(data):
    try:
        return risky_operation(data)
    except ValueError as e:
        print(f"记录错误:{e}")
        raise          # 原样向上抛

抛出不同的异常 / 异常链 raise … from

把底层异常转换为对调用方更有意义的异常时,用 raise NewError(...) from e 保留原因链:

Python6 行
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 e
  • raise 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 网络连接失败 / 超时 重试 + 指数退避

典型触发场景速览:

Python11 行
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:迭代器已耗尽

捕获的典型写法:

Python12 行
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__ 定制显示:

Python12 行
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}"

使用:

Python17 行
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 断言

Python3 行
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),逐项防御后统一兜底:

Python12 行
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          # 解析失败用默认值,不抛给调用方

交互式输入用循环重试,直到拿到合法值:

Python16 行
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:

Python9 行
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()

自定义上下文管理器只需实现两个方法:

Python8 行
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 提供的便捷工具:

Python15 行
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(...)

异常处理最佳实践

  1. 捕获具体异常,不用裸 except,不用过宽的 except Exception 掩盖 bug。
  2. 不吞异常:至少记日志;确实要忽略就用 contextlib.suppress 并写明原因。
  3. 在合适的层级处理:底层函数不处理、让它抛出;在能给出用户提示或回滚操作的边界层(如控制器、main)统一处理。
  4. 提供有用的错误信息:包含出错的值与合法范围,如 raise ValueError(f"无效年龄:{age},必须在 0-150 之间")。
  5. 用 finally / with 清理资源,不要靠“正常流程末尾关闭”。
  6. 记录日志而非 print,用 logging 保留堆栈:
Python13 行
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                                  # 需要时继续向上抛

调试时取堆栈字符串:

Python6 行
try:
    problematic_code()
except Exception as e:
    print(type(e).__name__, e)      # 类型与消息
    traceback.print_exc()           # 直接打印完整堆栈
    trace = traceback.format_exc()  # 拿到字符串,可写入日志或上报

实战例子

例1:安全的文件读取

Python18 行
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:网络请求重试(指数退避)

Python18 行
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:批量处理,逐项容错

Python19 行
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):对象的模板;对象:类的实例;属性:数据;方法:行为
  • 把相关数据和功能打包进对象,解决“数据分散、变量爆炸、难以维护”的问题
Python14 行
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 章)。

面向过程按步骤组织,面向对象按“谁有什么数据和行为”组织:简单脚本用面向过程,大型/多人协作项目用面向对象,实际项目两者结合。

Python11 行
# 面向过程: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__ 与初始化

Python12 行
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 = balance

self 的作用:方法内通过它读写实例自己的状态,实例之间互不影响。

Python8 行
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() 不访问实例/类,只是挂靠在类上的普通函数
Python18 行
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 不变)。

Python18 行
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

私有属性与名称改写

Python12 行
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 上下文管理,负责资源获取与释放
Python29 行
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__)

Python48 行
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

购物车系统(组合:购物车持有商品对象)

Python59 行
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

图书管理系统(对象组合 + 状态维护)

Python50 行
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、可变类属性共享、可变默认参数。

面向对象进阶

继承

子类获得父类的属性和方法,只写自己特有的部分,从而复用代码、统一修改。

Python5 行
class 父类名:      # 基类 / 超类(Base、Superclass)
    pass

class 子类名(父类名):   # 派生类(Derived、Subclass)
    pass
Python20 行
class 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__)。

Python12 行
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

Python7 行
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 = name

Python 用 C3 线性化得到方法解析顺序(MRO),同名方法按 类名.__mro__ 的顺序取第一个。

Python11 行
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 往后走”,这是菱形继承中每个类只执行一次的关键。

多态与鸭子类型

同一接口、不同实现;调用方只关心方法名,不关心具体类型。

Python11 行
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())

鸭子类型:只要对象有对应方法就能用,无需公共基类或接口声明。

Python5 行
def make_it_quack(thing):
    thing.quack()          # 不检查类型,只看有没有 quack

make_it_quack(Duck())      # 嘎嘎嘎
make_it_quack(Person())    # 人模仿鸭子叫

封装与 property

Python13 行
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 让“方法”用起来像“属性”,同时保留校验与只读控制。

Python22 行
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]:

Python5 行
@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 定义接口:抽象类不能实例化,子类必须实现所有抽象方法。

Python15 行
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() + 多态)

Python60 行
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 + 模板方法)

Python65 行
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 夹取 + 子类差异化)

Python47 行
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 · 日期时间

Python9 行
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 字符串→对象,格式码须一一对应。

Python4 行
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。

Python3 行
now + timedelta(days=1, hours=2)      # 也支持 weeks/minutes/seconds
(end - start).days                     # 相差天数
(end - start).total_seconds()          # 相差总秒数

collections · 专业容器

Counter — 计数。

Python7 行
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。

Python4 行
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 — 命名字段的元组,可索引也可属性访问。

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

Python4 行
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 · 随机数

Python10 行
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 · 迭代工具

Python16 行
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 · 函数工具

Python11 行
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 · 现代路径

Python8 行
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 · 系统接口

Python13 行
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 表示异常退出

其他常用标准库

Python3 行
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"))
Python5 行
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())
Python3 行
import re   # 详见第 23 章
re.findall(r"\d+", "a1b22")            # ['1', '22']
re.sub(r"\s+", " ", text)              # 替换
Python3 行
import json
json.dumps({"name": "张三"}, ensure_ascii=False)   # '{"name": "张三"}'
json.loads(s); json.dump(data, f); json.load(f)    # 文件读写
Python7 行
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"]      # 每行是 dict
Python3 行
import subprocess
r = subprocess.run(["git", "status"], capture_output=True, text=True, check=True)
r.returncode, r.stdout, r.stderr      # 优先用列表传参,避免 shell 注入
Python3 行
import logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
logging.info("启动完成")               # 用 logging 而非 print 输出运行信息
Python4 行
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 3
Python5 行
import 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)

Python25 行
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)

Python26 行
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)

Python14 行
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+" 才等价)。

Python2 行
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
Python7 行
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']

字符类

Python6 行
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 次

贪婪与非贪婪:量词默认贪婪(尽可能长),加 ? 变非贪婪(尽可能短)。

Python3 行
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']

锚点与边界

Python7 行
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

分组与捕获

Python9 行
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):匹配与前面分组相同的文本(查重复词)。
  • 非捕获分组 (?:...):只分组不捕获,减少干扰。

预查(零宽断言)

不消耗字符,只要求前后满足条件。

Python4 行
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 模块函数

Python18 行
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 忽略空白并支持 # 注释
Python9 行
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
Python8 行
# 多格式日期提取与归一
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:手机号 / 邮箱验证与脱敏

Python14 行
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)

Python18 行
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:数据清洗

Python19 行
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 处理

基本操作

Python15 行
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。

文件读写

Python7 行
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。

自定义对象序列化

Python26 行
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 处理

读取

Python13 行
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["年龄"])     # 值全是字符串

写入

Python13 行
# 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 可强制加引号

分隔符与引号

Python2 行
csv.reader(f, delimiter="\t")     # TSV:Tab 分隔
csv.reader(f, quotechar="'")      # 自定义引号字符(默认 ")

实用例子

例子1:JSON 与 CSV 互转

Python15 行
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 配置管理器

Python25 行
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 模块

最佳实践

Python11 行
# 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