python-gotchas
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseQuick Reference
快速参考
| Gotcha | Problem | Fix |
|---|---|---|
| Mutable default | | Use |
| Iterate + mutate | Skips items | Iterate over copy |
| Identity vs value | Use |
| Late binding | | |
| Float precision | | |
| Dict mutation | RuntimeError | |
| Class attribute | Shared mutable | Init in |
| Falsy Values | Examples |
|---|---|
| Boolean | |
| None | |
| Numbers | |
| Empty collections | |
| Scope Rule | Order |
|---|---|
| LEGB | Local → Enclosing → Global → Built-in |
| Access module-level variable |
| Access enclosing function variable |
| 陷阱类型 | 问题 | 修复方案 |
|---|---|---|
| Mutable default | | 使用 |
| Iterate + mutate | 跳过元素 | 迭代副本 |
| 身份标识 vs 值 | 仅对 |
| Late binding | | |
| Float precision | | |
| Dict mutation | 运行时错误 | |
| Class attribute | 可变属性共享 | 在 |
| 假值 | 示例 |
|---|---|
| 布尔值 | |
| None | |
| 数字 | |
| 空集合 | |
| 作用域规则 | 优先级 |
|---|---|
| LEGB | 局部 → 嵌套 → 全局 → 内置 |
| 访问模块级变量 |
| 访问嵌套函数变量 |
When to Use This Skill
何时使用本指南
Use for debugging and prevention:
- Understanding why code behaves unexpectedly
- Avoiding common Python pitfalls
- Reviewing code for subtle bugs
- Learning Python's evaluation rules
- Fixing mutable default arguments
Related skills:
- For fundamentals: see
python-fundamentals-313 - For testing: see
python-testing - For type hints: see
python-type-hints
用于调试和预防场景:
- 理解代码行为异常的原因
- 避免常见Python陷阱
- 检查代码中的潜在bug
- 学习Python的求值规则
- 修复可变默认参数问题
相关技能:
- 基础内容:查看
python-fundamentals-313 - 测试内容:查看
python-testing - 类型提示:查看
python-type-hints
Python Common Gotchas and Pitfalls
Python常见陷阱与误区
Overview
概述
Python has several well-known pitfalls that trip up developers of all experience levels. Understanding these gotchas prevents subtle bugs and unexpected behavior.
Python存在一些广为人知的陷阱,会困扰各个经验水平的开发者。了解这些陷阱可以避免潜在的bug和意外行为。
1. Mutable Default Arguments
1. Mutable Default Arguments
The Problem
问题
python
undefinedpython
undefinedBAD: Mutable default argument
BAD: Mutable default argument
def add_item(item, items=[]):
items.append(item)
return items
def add_item(item, items=[]):
items.append(item)
return items
Unexpected behavior!
Unexpected behavior!
print(add_item("a")) # ['a']
print(add_item("b")) # ['a', 'b'] - NOT ['b']!
print(add_item("c")) # ['a', 'b', 'c']
undefinedprint(add_item("a")) # ['a']
print(add_item("b")) # ['a', 'b'] - NOT ['b']!
print(add_item("c")) # ['a', 'b', 'c']
undefinedWhy It Happens
原因
Default arguments are evaluated once when the function is defined, not each time it's called. The same list object is reused across all calls.
默认参数在函数定义时仅求值一次,而非每次调用时求值。同一个列表对象会在所有调用中被复用。
The Fix
修复方案
python
undefinedpython
undefinedGOOD: Use None as default
GOOD: Use None as default
def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return items
def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return items
Works correctly
Works correctly
print(add_item("a")) # ['a']
print(add_item("b")) # ['b']
print(add_item("c")) # ['c']
undefinedprint(add_item("a")) # ['a']
print(add_item("b")) # ['b']
print(add_item("c")) # ['c']
undefinedOther Mutable Defaults
其他可变默认值
python
undefinedpython
undefinedBAD: All mutable types have this issue
BAD: All mutable types have this issue
def bad_dict(data={}): ...
def bad_set(data=set()): ...
def bad_class(config=SomeClass()): ...
def bad_dict(data={}): ...
def bad_set(data=set()): ...
def bad_class(config=SomeClass()): ...
GOOD: Always use None
GOOD: Always use None
def good_dict(data=None):
if data is None:
data = {}
return data
def good_set(data=None):
if data is None:
data = set()
return data
undefineddef good_dict(data=None):
if data is None:
data = {}
return data
def good_set(data=None):
if data is None:
data = set()
return data
undefined2. Mutating Lists While Iterating
2. Mutating Lists While Iterating
The Problem
问题
python
undefinedpython
undefinedBAD: Modifying list during iteration
BAD: Modifying list during iteration
numbers = [1, 2, 3, 4, 5, 6]
for num in numbers:
if num % 2 == 0:
numbers.remove(num)
print(numbers) # [1, 3, 5] - missed 4!
undefinednumbers = [1, 2, 3, 4, 5, 6]
for num in numbers:
if num % 2 == 0:
numbers.remove(num)
print(numbers) # [1, 3, 5] - missed 4!
undefinedWhy It Happens
原因
The iterator uses indices internally. When you remove an item, all subsequent indices shift, causing items to be skipped.
迭代器内部使用索引。当删除元素时,后续所有索引都会偏移,导致元素被跳过。
The Fixes
修复方案
python
undefinedpython
undefinedGOOD: Iterate over a copy
GOOD: Iterate over a copy
numbers = [1, 2, 3, 4, 5, 6]
for num in numbers[:]: # Slice creates a copy
if num % 2 == 0:
numbers.remove(num)
print(numbers) # [1, 3, 5]
numbers = [1, 2, 3, 4, 5, 6]
for num in numbers[:]: # Slice creates a copy
if num % 2 == 0:
numbers.remove(num)
print(numbers) # [1, 3, 5]
GOOD: Use list comprehension (preferred)
GOOD: Use list comprehension (preferred)
numbers = [1, 2, 3, 4, 5, 6]
numbers = [num for num in numbers if num % 2 != 0]
print(numbers) # [1, 3, 5]
numbers = [1, 2, 3, 4, 5, 6]
numbers = [num for num in numbers if num % 2 != 0]
print(numbers) # [1, 3, 5]
GOOD: Use filter
GOOD: Use filter
numbers = [1, 2, 3, 4, 5, 6]
numbers = list(filter(lambda x: x % 2 != 0, numbers))
print(numbers) # [1, 3, 5]
numbers = [1, 2, 3, 4, 5, 6]
numbers = list(filter(lambda x: x % 2 != 0, numbers))
print(numbers) # [1, 3, 5]
GOOD: Iterate backwards (for in-place modification)
GOOD: Iterate backwards (for in-place modification)
numbers = [1, 2, 3, 4, 5, 6]
for i in range(len(numbers) - 1, -1, -1):
if numbers[i] % 2 == 0:
del numbers[i]
print(numbers) # [1, 3, 5]
undefinednumbers = [1, 2, 3, 4, 5, 6]
for i in range(len(numbers) - 1, -1, -1):
if numbers[i] % 2 == 0:
del numbers[i]
print(numbers) # [1, 3, 5]
undefined3. is
vs ==
is==3. is
vs ==
is==The Problem
问题
python
undefinedpython
undefinedComparing values vs identity
Comparing values vs identity
a = [1, 2, 3]
b = [1, 2, 3]
print(a == b) # True - same values
print(a is b) # False - different objects
a = [1, 2, 3]
b = [1, 2, 3]
print(a == b) # True - same values
print(a is b) # False - different objects
Integer interning gotcha
Integer interning gotcha
x = 256
y = 256
print(x is y) # True (integers -5 to 256 are interned)
x = 257
y = 257
print(x is y) # False! (outside interning range)
undefinedx = 256
y = 256
print(x is y) # True (integers -5 to 256 are interned)
x = 257
y = 257
print(x is y) # False! (outside interning range)
undefinedThe Rule
规则
- Use to compare values
== - Use only for identity (singletons like
is,None,True)False
python
undefined- 使用比较值
== - 仅对身份标识(如、
None、True等单例对象)使用Falseis
python
undefinedGOOD: Correct usage
GOOD: Correct usage
if value is None:
...
if value == other_value:
...
if value is None:
...
if value == other_value:
...
BAD: Don't use is
for value comparison
isBAD: Don't use is
for value comparison
isif value is 0: # Wrong!
...
undefinedif value is 0: # Wrong!
...
undefined4. Variable Scope (LEGB)
4. Variable Scope (LEGB)
The Problem
问题
python
undefinedpython
undefinedClosure gotcha
Closure gotcha
functions = []
for i in range(3):
functions.append(lambda: i)
functions = []
for i in range(3):
functions.append(lambda: i)
All return the same value!
All return the same value!
print([f() for f in functions]) # [2, 2, 2]
undefinedprint([f() for f in functions]) # [2, 2, 2]
undefinedWhy It Happens
原因
The lambda captures the variable , not its value. By the time lambdas are called, is 2.
iiLambda表达式捕获的是变量,而非其值。当Lambda被调用时,的值已经是2。
iiThe Fixes
修复方案
python
undefinedpython
undefinedGOOD: Capture value with default argument
GOOD: Capture value with default argument
functions = []
for i in range(3):
functions.append(lambda i=i: i) # Default arg captures value
print([f() for f in functions]) # [0, 1, 2]
functions = []
for i in range(3):
functions.append(lambda i=i: i) # Default arg captures value
print([f() for f in functions]) # [0, 1, 2]
GOOD: Use functools.partial
GOOD: Use functools.partial
from functools import partial
def return_value(x):
return x
functions = [partial(return_value, i) for i in range(3)]
print([f() for f in functions]) # [0, 1, 2]
undefinedfrom functools import partial
def return_value(x):
return x
functions = [partial(return_value, i) for i in range(3)]
print([f() for f in functions]) # [0, 1, 2]
undefinedUnboundLocalError
UnboundLocalError
python
undefinedpython
undefinedBAD: This raises UnboundLocalError
BAD: This raises UnboundLocalError
x = 10
def increment():
x = x + 1 # Error! x is local but used before assignment
return x
x = 10
def increment():
x = x + 1 # Error! x is local but used before assignment
return x
GOOD: Use global (sparingly)
GOOD: Use global (sparingly)
def increment():
global x
x = x + 1
return x
def increment():
global x
x = x + 1
return x
BETTER: Avoid global, pass as parameter
BETTER: Avoid global, pass as parameter
def increment(x):
return x + 1
undefineddef increment(x):
return x + 1
undefined5. String Concatenation
5. String Concatenation
Implicit Concatenation Gotcha
Implicit Concatenation Gotcha
python
undefinedpython
undefinedMissing comma creates concatenation
Missing comma creates concatenation
items = [
"apple"
"banana" # Oops! Missing comma
"cherry"
]
print(items) # ['applebanana', 'cherry']
items = [
"apple"
"banana" # Oops! Missing comma
"cherry"
]
print(items) # ['applebanana', 'cherry']
CORRECT
CORRECT
items = [
"apple",
"banana",
"cherry",
]
undefineditems = [
"apple",
"banana",
"cherry",
]
undefinedType Mixing
Type Mixing
python
undefinedpython
undefinedBAD: Can't concatenate str and int
BAD: Can't concatenate str and int
name = "User"
count = 42
name = "User"
count = 42
message = "Hello " + name + ", you have " + count + " messages" # TypeError!
message = "Hello " + name + ", you have " + count + " messages" # TypeError!
GOOD: Use f-strings
GOOD: Use f-strings
message = f"Hello {name}, you have {count} messages"
message = f"Hello {name}, you have {count} messages"
GOOD: Use str()
GOOD: Use str()
message = "Hello " + name + ", you have " + str(count) + " messages"
undefinedmessage = "Hello " + name + ", you have " + str(count) + " messages"
undefined6. Late Binding in Closures
6. Late Binding in Closures
The Problem
问题
python
undefinedpython
undefinedClass method gotcha
Class method gotcha
class MyClass:
def init(self, callbacks=[]): # BAD: Mutable default!
self.callbacks = callbacks
def add_callback(self, func):
self.callbacks.append(func)obj1 = MyClass()
obj2 = MyClass()
obj1.add_callback(lambda: print("Hello"))
class MyClass:
def init(self, callbacks=[]): # BAD: Mutable default!
self.callbacks = callbacks
def add_callback(self, func):
self.callbacks.append(func)obj1 = MyClass()
obj2 = MyClass()
obj1.add_callback(lambda: print("Hello"))
obj2 also has the callback!
obj2 also has the callback!
print(len(obj2.callbacks)) # 1
undefinedprint(len(obj2.callbacks)) # 1
undefinedThe Fix
修复方案
python
class MyClass:
def __init__(self, callbacks=None):
self.callbacks = callbacks if callbacks is not None else []
def add_callback(self, func):
self.callbacks.append(func)python
class MyClass:
def __init__(self, callbacks=None):
self.callbacks = callbacks if callbacks is not None else []
def add_callback(self, func):
self.callbacks.append(func)7. Boolean Evaluation
7. Boolean Evaluation
Falsy Values
Falsy Values
python
undefinedpython
undefinedThese are all falsy
These are all falsy
falsy_values = [
False,
None,
0,
0.0,
0j,
"",
[],
{},
set(),
range(0),
]
falsy_values = [
False,
None,
0,
0.0,
0j,
"",
[],
{},
set(),
range(0),
]
Gotcha: Empty collections are falsy
Gotcha: Empty collections are falsy
data = []
if data:
print("Has data") # Not printed
else:
print("No data") # Printed
data = []
if data:
print("Has data") # Not printed
else:
print("No data") # Printed
But None and empty are different!
But None and empty are different!
if data is None:
print("Is None") # Not printed
elif data == []:
print("Is empty list") # Printed
undefinedif data is None:
print("Is None") # Not printed
elif data == []:
print("Is empty list") # Printed
undefinedExplicit Checks
Explicit Checks
python
undefinedpython
undefinedBAD: Ambiguous check
BAD: Ambiguous check
def process(items):
if not items: # Could be None OR empty
return
def process(items):
if not items: # Could be None OR empty
return
GOOD: Be explicit about what you're checking
GOOD: Be explicit about what you're checking
def process(items):
if items is None:
raise ValueError("items cannot be None")
if len(items) == 0:
return # Early return for empty list
undefineddef process(items):
if items is None:
raise ValueError("items cannot be None")
if len(items) == 0:
return # Early return for empty list
undefined8. Floating Point Precision
8. Floating Point Precision
The Problem
问题
python
undefinedpython
undefinedFloating point arithmetic isn't exact
Floating point arithmetic isn't exact
print(0.1 + 0.2) # 0.30000000000000004
print(0.1 + 0.2 == 0.3) # False!
undefinedprint(0.1 + 0.2) # 0.30000000000000004
print(0.1 + 0.2 == 0.3) # False!
undefinedThe Fixes
修复方案
python
import math
from decimal import Decimalpython
import math
from decimal import DecimalGOOD: Use math.isclose for comparisons
GOOD: Use math.isclose for comparisons
print(math.isclose(0.1 + 0.2, 0.3)) # True
print(math.isclose(0.1 + 0.2, 0.3)) # True
GOOD: Use Decimal for financial calculations
GOOD: Use Decimal for financial calculations
price = Decimal("19.99")
tax = Decimal("0.0875")
total = price * (1 + tax)
print(total) # 21.739125
price = Decimal("19.99")
tax = Decimal("0.0875")
total = price * (1 + tax)
print(total) # 21.739125
Round appropriately
Round appropriately
print(round(total, 2)) # 21.74
undefinedprint(round(total, 2)) # 21.74
undefined9. Exception Handling
9. Exception Handling
Bare Except
Bare Except
python
undefinedpython
undefinedBAD: Catches everything, including KeyboardInterrupt
BAD: Catches everything, including KeyboardInterrupt
try:
risky_operation()
except:
pass
try:
risky_operation()
except:
pass
BAD: Too broad
BAD: Too broad
try:
risky_operation()
except Exception:
pass # Silently ignores all errors
try:
risky_operation()
except Exception:
pass # Silently ignores all errors
GOOD: Catch specific exceptions
GOOD: Catch specific exceptions
try:
risky_operation()
except ValueError as e:
logger.error(f"Invalid value: {e}")
except ConnectionError as e:
logger.error(f"Connection failed: {e}")
raise # Re-raise after logging
undefinedtry:
risky_operation()
except ValueError as e:
logger.error(f"Invalid value: {e}")
except ConnectionError as e:
logger.error(f"Connection failed: {e}")
raise # Re-raise after logging
undefinedException Variable Scope
Exception Variable Scope
python
undefinedpython
undefinedPython 3: Exception variable is deleted after except block
Python 3: Exception variable is deleted after except block
try:
1 / 0
except ZeroDivisionError as e:
error = e # Save if needed
print(e)
try:
1 / 0
except ZeroDivisionError as e:
error = e # Save if needed
print(e)
print(e) # NameError! e is deleted
print(e) # NameError! e is deleted
print(error) # OK
undefinedprint(error) # OK
undefined10. Dictionary Key Ordering
10. Dictionary Key Ordering
Modern Python (3.7+)
Modern Python (3.7+)
python
undefinedpython
undefinedSince Python 3.7, dicts maintain insertion order
Since Python 3.7, dicts maintain insertion order
d = {"b": 2, "a": 1, "c": 3}
print(list(d.keys())) # ['b', 'a', 'c'] - insertion order
d = {"b": 2, "a": 1, "c": 3}
print(list(d.keys())) # ['b', 'a', 'c'] - insertion order
But comparison ignores order
But comparison ignores order
d1 = {"a": 1, "b": 2}
d2 = {"b": 2, "a": 1}
print(d1 == d2) # True
undefinedd1 = {"a": 1, "b": 2}
d2 = {"b": 2, "a": 1}
print(d1 == d2) # True
undefinedGotcha: Changing Dict During Iteration
Gotcha: Changing Dict During Iteration
python
undefinedpython
undefinedBAD: RuntimeError
BAD: RuntimeError
d = {"a": 1, "b": 2, "c": 3}
for key in d:
if d[key] == 2:
del d[key] # RuntimeError: dictionary changed size during iteration
d = {"a": 1, "b": 2, "c": 3}
for key in d:
if d[key] == 2:
del d[key] # RuntimeError: dictionary changed size during iteration
GOOD: Iterate over copy of keys
GOOD: Iterate over copy of keys
d = {"a": 1, "b": 2, "c": 3}
for key in list(d.keys()):
if d[key] == 2:
del d[key]
print(d) # {'a': 1, 'c': 3}
d = {"a": 1, "b": 2, "c": 3}
for key in list(d.keys()):
if d[key] == 2:
del d[key]
print(d) # {'a': 1, 'c': 3}
GOOD: Dict comprehension
GOOD: Dict comprehension
d = {"a": 1, "b": 2, "c": 3}
d = {k: v for k, v in d.items() if v != 2}
undefinedd = {"a": 1, "b": 2, "c": 3}
d = {k: v for k, v in d.items() if v != 2}
undefined11. Import Gotchas
11. Import Gotchas
Circular Imports
Circular Imports
python
undefinedpython
undefinedmodule_a.py
module_a.py
from module_b import func_b # Fails if module_b imports from module_a
def func_a():
return func_b()
from module_b import func_b # Fails if module_b imports from module_a
def func_a():
return func_b()
SOLUTION: Import inside function
SOLUTION: Import inside function
def func_a():
from module_b import func_b
return func_b()
def func_a():
from module_b import func_b
return func_b()
OR: Import module, not function
OR: Import module, not function
import module_b
def func_a():
return module_b.func_b()
undefinedimport module_b
def func_a():
return module_b.func_b()
undefinedModule Name Shadowing
Module Name Shadowing
python
undefinedpython
undefinedBAD: File named random.py shadows stdlib
BAD: File named random.py shadows stdlib
random.py (your file)
random.py (your file)
import random # Imports YOUR file, not stdlib!
random.randint(1, 10) # AttributeError
import random # Imports YOUR file, not stdlib!
random.randint(1, 10) # AttributeError
Python 3.13 now warns about this!
Python 3.13 now warns about this!
undefinedundefined12. Class Attribute vs Instance Attribute
12. Class Attribute vs Instance Attribute
python
class MyClass:
shared_list = [] # Class attribute - shared by all instances!
def add_item(self, item):
self.shared_list.append(item)
a = MyClass()
b = MyClass()
a.add_item("hello")
print(b.shared_list) # ['hello'] - Oops!python
class MyClass:
shared_list = [] # Class attribute - shared by all instances!
def add_item(self, item):
self.shared_list.append(item)
a = MyClass()
b = MyClass()
a.add_item("hello")
print(b.shared_list) # ['hello'] - Oops!GOOD: Initialize in init
GOOD: Initialize in init
class MyClass:
def init(self):
self.items = [] # Instance attribute - unique to each instance
def add_item(self, item):
self.items.append(item)undefinedclass MyClass:
def init(self):
self.items = [] # Instance attribute - unique to each instance
def add_item(self, item):
self.items.append(item)undefinedQuick Reference
快速参考
| Gotcha | Problem | Solution |
|---|---|---|
| Mutable defaults | | Use |
| Mutating while iterating | Skips items | Iterate over copy or use comprehension |
| Identity vs equality | Use |
| Late binding | Captures variable, not value | Use default argument to capture |
| Falsy values | Empty != None | Be explicit in checks |
| Float precision | 0.1 + 0.2 != 0.3 | Use |
| Bare except | Catches too much | Catch specific exceptions |
| Dict iteration | Can't modify during | Iterate over |
| Circular imports | Import errors | Import inside function or import module |
| Class attributes | Shared unexpectedly | Initialize in |
| 陷阱类型 | 问题 | 解决方案 |
|---|---|---|
| Mutable defaults | | 使用 |
| Mutating while iterating | 跳过元素 | 迭代副本或使用推导式 |
| 身份标识 vs 相等性 | 仅对 |
| Late binding | 捕获变量而非值 | 使用默认参数捕获值 |
| Falsy values | 空值不等于None | 检查时明确判断 |
| Float precision | 0.1 + 0.2 != 0.3 | 使用 |
| Bare except | 捕获范围过广 | 捕获特定异常 |
| Dict iteration | 迭代时无法修改 | 迭代 |
| Circular imports | 导入错误 | 在函数内导入或导入整个模块 |
| Class attributes | 意外共享属性 | 在 |