Note Default values are evaluated once at function definition time, not on each call. Mutable defaults (list, dict, set) are shared across calls. Always use None as default.
mutable default argumentdefault list bugshared defaultfunction default gotcha
Late Binding in Closures
syntax
# BAD: variable captured by reference# GOOD: use default argument to capture value
example
# WRONG: All lambdas see final value of i
funcs_bad = [lambda: i for i in range(4)]
print([f() for f in funcs_bad])
# CORRECT: Capture current value via default arg
funcs_good = [lambda i=i: i for i in range(4)]
print([f() for f in funcs_good])
output
[3, 3, 3, 3]
[0, 1, 2, 3]
Note Python closures capture variables by reference, not by value. The loop variable's final value is what all closures see. The i=i default-argument trick forces a copy at each iteration.
late bindingclosure buglambda in loopcaptured variable reference
Modifying a List During Iteration
syntax
# BAD: for x in items: items.remove(x)# GOOD: iterate over a copy or build a new list
example
# WRONG: Modifying during iteration skips elements
nums = [1, 2, 3, 4, 5, 6]
for n in nums:
if n % 2 == 0:
nums.remove(n)
print(f"Wrong: {nums}") # 4 was skipped!# CORRECT: List comprehension to filter
nums = [1, 2, 3, 4, 5, 6]
result = [n for n in nums if n % 2 != 0]
print(f"Right: {result}")
output
Wrong: [1, 3, 5]
Right: [1, 3, 5]
Note Removing items while iterating shifts indices and causes skipped elements. Use a comprehension, filter(), or iterate over a copy (list[:]) instead.
modify list during loopremove while iteratinglist mutation bugskip elements
is vs == (Identity vs Equality)
syntax
a == b # equal values
a is b # same object
example
a = [1, 2, 3]
b = [1, 2, 3]
print(a == b)
print(a is b)
# CPython caches small integers
x = 256
y = 256print(x is y)
x = 257
y = 257print(x is y) # May be False!
output
True
False
True
False
Note Use == for value comparison. Use 'is' only for None, True, False, or when you specifically need identity checks. CPython caches integers -5 to 256, making 'is' unreliable for numbers.
is vs equalsidentity vs equalityobject comparison== vs is
Global vs Local Variable Confusion
syntax
global var_name # to modify a global
nonlocal var_name # to modify enclosing scope
Note If a function assigns to a variable, Python treats it as local in the entire function. Reading a global is fine, but modifying requires 'global'. For enclosing scopes, use 'nonlocal'.
global variablenonlocalUnboundLocalErrorscope confusionvariable scope
Shallow vs Deep Copy
syntax
import copy
shallow = copy.copy(obj)
deep = copy.deepcopy(obj)
Note list.copy(), dict.copy(), and slicing (lst[:]) all produce shallow copies. Nested mutable objects are still shared. Use copy.deepcopy() when you need fully independent nested structures.
shallow copydeep copycopy listnested list copycopy module
Forgetting Strings Are Immutable
syntax
# BAD: s.replace(...) without reassigning# GOOD: s = s.replace(...)
example
name = " Alice "# WRONG: result is discarded
name.strip()
print(f"'{name}'") # unchanged!# CORRECT: reassign the result
name = name.strip()
print(f"'{name}'")
output
' Alice '
'Alice'
Note All string methods return new strings. Forgetting to capture the return value is a very common bug. The same applies to str.replace(), str.lower(), etc.
string immutablestrip not workingreplace not workingstring method return
Truthiness Gotchas
syntax
# Falsy: None, False, 0, 0.0, '', [], {}, set()
example
def process(data=None):
# WRONG: if not data# This also catches empty list [], 0, and ""ifnot data:
print("No data") # Triggered by empty list too!# CORRECT: if data is Noneif data is None:
print("Data is None")
process([]) # Might not intend to reject []
process(None)
process(0)
output
No data
No data
Data is None
No data
Note Empty collections, zero, empty string, and None are all falsy. When you specifically mean 'no value provided', check 'is None' rather than relying on truthiness.
truthinessfalsy valuesempty list truthyNone checkbool gotcha