跳转至

第 12 章 · 常见陷阱与反 Java 习惯

本章是"反 Java 习惯"清单——把前面各章散落的陷阱汇总,并补充新条目。每条给 ❌ 反例✅ 正解。建议把它当备忘录,写完代码回来过一遍。


12.1 比较与真值

==None

if x == None: ...     # ❌ PEP 8 禁止;is 表达"同一单例",语义更准(详见第 3 章)
if x is None: ...     # ✅ 判 None 一律用 is

if x == True:

if ok == True: ...    # ❌ 1 == True 为真,隐式数值比较
if ok: ...            # ✅ 用 truthiness

字符串/列表用 == 比内容(Java 直觉相反)

# Java: a == b 比引用,是著名 bug
# Python: a == b 比值,正是你想要的 ✅
assert "abc" == "abc"
assert [1, 2] == [1, 2]

浮点直接比相等

if 0.1 + 0.2 == 0.3: ...      # ❌ False(浮点误差)
import math
if math.isclose(0.1 + 0.2, 0.3): ...   # ✅

12.2 可变性与拷贝

可变默认参数(第 2 章

def f(items=[]): ...          # ❌ 默认值共享
def f(items=None):            # ✅ None 哨兵
    if items is None: items = []

b = a 当拷贝用(第 3 章

b = a; b.append(x)            # ❌ a 也变
b = a.copy()                  # ✅ 浅拷贝(一层)
b = copy.deepcopy(a)          # ✅ 深拷贝(嵌套)

tuple 装可变对象

t = ([1],)
t[0].append(2)                # ✅ 合法!tuple"不可变"只指引用不变
# 别以为 tuple 里东西就动不了

大量字符串 + 拼接

s = ""; for w in words: s += w   # ❌ 每次建新对象,O(n²)
s = "".join(words)               # ✅ 一次拼接

12.3 语法与习惯

for i in range(len(x)) 取索引

for i in range(len(xs)): print(xs[i])   # ❌ Java 习惯
for x in xs: print(x)                   # ✅ 直接遍历
for i, x in enumerate(xs): print(i, x)  # ✅ 要索引用 enumerate

不用 with 打开文件

f = open(path); data = f.read(); f.close()   # ❌ 异常时忘记关闭
with open(path) as f: data = f.read()        # ✅ 自动关闭

camelCase 命名

def calculateTotal(): ...    # ❌ Java 风格
def calculate_total(): ...   # ✅ Python 风格 snake_case

手写 switch 链 / 手动索引取值

# ❌ if/elif 长链做值映射
if op == "add": ...
elif op == "sub": ...
# ✅ 字典分发 或 match(第 1 章)
actions = {"add": do_add, "sub": do_sub}
actions[op]()

12.4 函数与作用域

lambda 晚绑定(第 2 章

funcs = [lambda: i for i in range(3)]
[f() for f in funcs]          # ❌ [2, 2, 2]
funcs = [lambda i=i: i for i in range(3)]   # ✅ 默认参数固化

滥用 lambda 写复杂逻辑

key = lambda u: (u["dept"], -u["salary"], u["name"].lower())   # ❌ 太长难读
def sort_key(u): return (u["dept"], -u["salary"], u["name"].lower())  # ✅ 命名函数

忽略返回值(函数默认返回 None

def add(a, b):
    a + b              # ❌ 漏了 return,返回 None
def add(a, b):
    return a + b       # ✅

except 吞一切(第 5 章

except: ...            # ❌ 连 Ctrl+C 都吞
except Exception: ...  # ✅ 只捕普通异常

try 块过大

try:                   # ❌ 把一大段成功代码塞进 try,误捕
    val = parse(x); result = process(val); save(result)
except Exception: ...
# ✅ 只包可能出错的那行,成功路径放 else

12.5 模块与工程化

文件名遮蔽标准库(第 6 章

把自己的脚本命名成 random.py / csv.py / math.py   # ❌ 遮蔽标准库
改名为 my_random.py                                # ✅

全局装包(第 10 章

pip install requests        # ❌(全局解释器里)
# ✅ 先 python -m venv .venv && 激活,再装;或用 uv

from x import *

from module import *        # ❌ 污染命名空间
from module import specific # ✅ 显式导入

groupby 没排序(第 9 章

for k, g in groupby(data, key): ...   # ❌ 只分相邻相同段
data = sorted(data, key=key)          # ✅ 先按 key 排序

12.6 并发

CPU 密集用多线程(第 11 章

threads = [Thread(target=heavy_cpu) for _ in range(4)]   # ❌ GIL,不并行
with Pool(4) as p: p.map(heavy_cpu, items)               # ✅ 多进程真并行

在循环里建大量进程/线程而不限流

for url in urls: Thread(target=fetch, args=(url,)).start()  # ❌ 线程爆炸
with ThreadPoolExecutor(max_workers=10) as ex: ...          # ✅ 限流

12.7 自检清单

写完一段 Python 代码,回来问自己:

  • 有没有用 == None?换成 is None
  • 有没有 for i in range(len(...))?换成直接遍历 / enumerate
  • 可变默认参数有没有用 None 哨兵?
  • 拼字符串有没有用 join
  • 文件/资源有没有用 with
  • try 块是不是太大了?except 是不是裸的?
  • 命名是不是 snake_case
  • CPU 密集任务有没有误用多线程?

心态转换

每条陷阱背后都是同一条原则:别用 Java 的肌肉记忆,用 Python 的方式。当你发现自己在"翻译 Java",停下来查一下有没有更 Pythonic 的写法——通常有。


上一章:第 11 章 · 并发模型 | 下一章:第 13 章 · 贯穿项目实战