完整讲解 · 5 段教学·配 4 道练习题·预计 40 分钟
本页是本章的通读版,可直接读完全部讲解。想动手写代码、跑判分,去 闯关模式。
学到这里,你已经能写出能跑的 Python 代码了。但「能跑」和「好读」是两回事。
看这段代码:
def calc(x, y, op):
if op == 'add':
return x + y
elif op == 'sub':
return x - y读到这个函数,你的第一反应是什么?
x 和 y 是数字还是字符串?op 还能传啥?None 怎么办?只能去翻调用方的代码——或者直接去问写代码的人。
代码不是只写给电脑看的——更多时候是写给:
所以「好读」是一项必须练的能力。
| 主题 | 解决什么问题 |
|---|---|
| 好命名 | 一眼看懂这个变量/函数是干嘛的 |
| 早返回 | 把 6 层嵌套的 if 摊平 |
| 推导式 | 把 4 行 for 循环压成 1 行 |
| 类型注解 | 让函数签名自带文档 |
学完这一节,你写出来的 Python 代码会专业一大截。
写代码 80% 的时间在「读」,只有 20% 在「写」。所以命名好不好,直接决定代码读起来累不累。
变量装的是「东西」——用名词:
user_name = '张三'
total_price = 100
order_list = []函数做的是「动作」——用动词或动词短语:
def get_user(uid): ...
def calculate_total(items): ...
def send_email(to, body): ...反例(让读者皱眉):
def user(uid): ... # 这是函数还是属性?
def data(items): ... # 处理 data?还是返回 data?is_ / has_ / can_ 开头is_active = True # 一眼看出是布尔
has_permission = False
can_edit = True反例:
active = True # active 是状态值还是布尔?要去看赋值才知道
permission = False # 这是权限对象还是 True/False?# 不好
def calc(u, p):
return u * p
# 好
def calculate_total(unit_price, quantity):
return unit_price * quantity注意:通用缩写(比如 i 当循环变量、url、id、db)大家都懂,可以用。但 usr、prc、amt 这种「省两个字母」的缩写就别写了,省的那点字符根本不值得读者去猜。
for i in range(10): # i 作用域很小,用一个字母没问题
print(i)
# 但模块级的全局变量就要起得清楚
USER_ROLE_ADMIN = 'admin'
DEFAULT_TIMEOUT_SECONDS = 30看到这个名字,读者还要不要去翻定义才能懂?要——名字没起好。
来看下面这段「校验登录」的代码:
def login(user, password):
if user is not None:
if user.is_active:
if user.check_password(password):
if not user.is_locked:
return '登录成功'
else:
return '账户已锁定'
else:
return '密码错误'
else:
return '账户未激活'
else:
return '用户不存在'四层嵌套——读到第三层 if 时,你已经记不清第一层的条件是什么了。
「先处理边界情况,主流程才能放在最外层」——这就是早返回的核心思想。
def login(user, password):
if user is None:
return '用户不存在'
if not user.is_active:
return '账户未激活'
if not user.check_password(password):
return '密码错误'
if user.is_locked:
return '账户已锁定'
return '登录成功'变化看出来了吗?
else凡是看到这种结构:
if 条件成立:
# 主流程一大段
...
else:
return 错误都可以改写成:
if not 条件成立:
return 错误
# 主流程一大段(无缩进)
...主流程从 1 层缩进退回到 0 层。多个条件叠加时效果尤为明显。
不是所有 if 都该早返回。如果两个分支地位平等——比如「奇数返回 X、偶数返回 Y」——那就老老实实写 if/else,没必要硬塞进早返回。
def parity(n):
if n % 2 == 0:
return '偶数'
else:
return '奇数'这样反而清楚。早返回是为了摊平错误处理,不是为了消灭所有 else。
Python 写「从一组数据生成另一组数据」这件事,有专门的优雅写法——推导式。
写一个「把 nums 里所有正数挑出来」的需求。老写法:
nums = [-2, 1, -3, 4, 0, -5, 6]
positives = []
for x in nums:
if x > 0:
positives.append(x)
print(positives)输出:
[1, 4, 6]
四行——其实就是说一句话:「把 nums 里 x > 0 的 x 收集成一个列表」。
列表推导式直接把这句话翻译成代码:
nums = [-2, 1, -3, 4, 0, -5, 6]
positives = [x for x in nums if x > 0]
print(positives)输出一样:
[1, 4, 6]
读法:「把 x 收集起来,对每个 x 来自 nums,且 x > 0」。
[表达式 for 变量 in 可迭代对象 if 条件]
举几个例子:
# 把每个数字平方
[x * x for x in [1, 2, 3, 4]] # [1, 4, 9, 16]
# 全部转大写
[s.upper() for s in ['ab', 'cd']] # ['AB', 'CD']
# 偶数的平方
[x * x for x in range(10) if x % 2 == 0] # [0, 4, 16, 36, 64]把一个字典的所有值翻倍,老写法:
prices = {'apple': 5, 'banana': 3, 'cherry': 10}
doubled = {}
for k, v in prices.items():
doubled[k] = v * 2
print(doubled)字典推导式:
prices = {'apple': 5, 'banana': 3, 'cherry': 10}
doubled = {k: v * 2 for k, v in prices.items()}
print(doubled)结构:{键: 值 for 变量 in 可迭代对象}。
推导式很爽,但不是越短越好。如果一行推导式塞了:
for 循环那就停下来——回到老老实实的 for 循环。一行写不下、要拐三个弯的推导式,比四行 for 循环难读多了。
记住这个标准:一眼能读懂——用推导式;要停下来翻译——用 for 循环。
回到最开始那段「让人懵」的代码:
def calc(x, y, op):
...加上类型注解之后:
def calc(x: int, y: int, op: str) -> int:
...光看签名你就懂了:「两个 int、一个 str,返回 int」。
记住这个口诀:「参数加冒号,返回加箭头」。
def add(a: int, b: int) -> int:
return a + b
def greet(name: str) -> str:
return f'你好,{name}'
def log(message: str) -> None: # 没有返回值就写 None
print(message)调用时:
print(add(3, 5))
print(greet('张三'))
log('打卡成功')输出:
8
你好,张三
打卡成功
注意一个重要的事实:Python 解释器不会根据注解去校验类型。
def add(a: int, b: int) -> int:
return a + b
print(add('hello', 'world')) # 居然能跑!输出 'helloworld'注解只是「写给人看的契约」+「写给工具看的提示」。要真的校验类型,需要用 mypy、pyright 这类静态检查工具。
那加注解还有意义吗?有:
| 类型 | 含义 |
|---|---|
int | 整数 |
float | 浮点数 |
str | 字符串 |
bool | 布尔值 |
list | 列表 |
dict | 字典 |
tuple | 元组 |
None | 空值(用作返回类型) |
光写 list 不够——「列表里装的啥」还是不知道。Python 3.9+ 可以这么写:
def total(nums: list[int]) -> int:
return sum(nums)
def name_to_age(data: dict[str, int]) -> int:
return len(data)list[int] —— 装 int 的列表dict[str, int] —— 键是 str、值是 int 的字典用 X | None(Python 3.10+):
def find_user(uid: int) -> str | None:
db = {1: '张三', 2: '李四'}
return db.get(uid) # 找不到时 .get() 返回 None读起来就是「返回 str 或者 None」,调用方一看就知道要判空。
优先级从高到低:
for i in range(10) 这种 i 显然是 int 的,硬塞 i: int 反而显得啰嗦。注解的价值是「消除歧义」,没歧义的地方加了反而碍眼。
读完了?动手练一遍才算真会。
去闯关模式练习 →