25 分钟
工程化

Python 常见陷阱与最佳实践

识别 Python 里最容易踩的坑,写地道、健壮的 Python 代码

  • 识别可变默认参数等经典陷阱
  • 掌握 Pythonic 写法
  • 学会防御性编程
  • 理解 Python 的隐式行为

陷阱 1:可变默认参数

这是 Python 最著名的坑。默认参数在函数定义时只计算一次,不是每次调用时创建:

示例代码(可运行)
⚠️为什么会这样?

def 定义的默认参数,在模块加载时就会求值一次,之后每次调用函数都会共享同一个对象。None 是不可变的单例,每次进函数检查到 None,就创建新列表,这是标准写法。

陷阱 2:浮点数精度

示例代码(可运行)

陷阱 3:循环中修改列表

示例代码(可运行)

陷阱 4:闭包延迟绑定

示例代码(可运行)

陷阱 5:is vs ==

示例代码(可运行)
💡PEP 8 真值判断

if x: 比 if x == True: 好;if not x: 比 if x == False: 好;if x is None: 和 if x is not None: 用于判断 None。不要写 if len(x) > 0:,直接写 if x:。

Pythonic 写法

Python 有自己的惯用法,写出「地道」的 Python 代码:

Python
# 1. 用 with 管理资源
with open("file.txt") as f:
    data = f.read()

# 2. 用 enumerate 同时取索引和值
for i, item in enumerate(items):
    print(i, item)

# 3. 用 zip 同时遍历多个列表
for name, score in zip(names, scores):
    print(name, score)

# 4. 用字典的 get 避免 KeyError
name = user.get("name", "匿名")  # 不存在时返回默认值

# 5. 用 in 判断成员(不要用 find + 索引)
if "python" in text:
    ...

# 6. 多重赋值
a, b = b, a  # 交换变量,不需要临时变量

# 7. 解包
first, *rest = [1, 2, 3, 4]  # first=1, rest=[2,3,4]

# 8. 用 any/all
if all(score > 60 for score in scores):  # 全部及格
    ...
if any(score < 60 for score in scores):  # 有人不及格
    ...

防御性编程清单

Python
# 1. 输入校验
def get_age(data):
    age = data.get("age")
    if age is None:
        raise ValueError("缺少 age 字段")
    if not isinstance(age, (int, float)):
        raise TypeError("age 应为数字,实际为 " + str(type(age)))
    if age < 0 or age > 150:
        raise ValueError("age 不合理: " + str(age))
    return age

# 2. 早返回(Guard Clause)减少嵌套
def process(user):
    if user is None:
        return
    if not user.is_active:
        return
    # 正常逻辑...

# 3. 不要裸 except
try:
    risky()
except Exception as e:
    logger.error("risky failed: %s", e)
    raise

# 4. 类型注解
def divide(a: float, b: float) -> float:
    if b == 0:
        raise ValueError("除数不能为0")
    return a / b
🐍资深工程师的习惯

写函数前先想清楚输入输出和异常情况。对外部数据(用户输入、API返回、文件内容)永远不信任。错误要明确、具体、可操作。日志记录足够上下文(谁、什么时候、什么数据、什么错误)。写完代码先自己审查一遍再提交。

找 Bug可变默认参数 [] 在函数定义时创建一次,所有调用共享。第一次 append_to(1) 后默认列表变成 [1],第二次传了新列表所以不受影响,第三次 append_to(3) 又往共享列表加,变成 [1, 3]。
def append_to(element, target=[]): target.append(element) return target print(append_to(1)) print(append_to(2, [])) print(append_to(3))

资深工程师加餐

底层原理 · 大厂视角 · 工程经验,点卡片展开

常见根因:内存泄漏导致 OOM 被系统杀掉、连接/文件句柄没关闭耗尽、事件循环里混入同步阻塞、未捕获异常让进程退出。12-Factor App 的建议很实用:配置环境化、日志当成事件流输出、进程保持无状态(状态放数据库/缓存),这样才能随时加副本做水平扩展。

挑战任务

找出陷阱

简单+50 XP

直接看代码,找里面的 Python 经典陷阱,把所有问题都修好,再给每个陷阱说清楚为啥会出问题。

找出陷阱
3 个测试用例

课后作业

代码审查练习

中等+30 XP

找你之前写的 Python 程序,检查这几个问题:可变默认参数、裸except、循环中修改列表、浮点数直接比较、过度嵌套。列出来,再修复。

代码审查练习
1 个测试用例