Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →Most Python variable bugs come from one misunderstanding: a name is a label attached to an object, not a box that holds its own copy of the data. Once that model is clear, the mistakes below stop looking random. The examples target Python 3. The list reflects common pitfalls that show up repeatedly in code and in the official documentation, arranged by theme. It is not a ranking by how often each mistake occurs.
The model behind most variable bugs
An assignment such as b = a binds the name b to the object that a already refers to. It does not create a new list, dictionary, or any other object. The Python Tutorial states the principle directly: assignments do not copy data; they bind names to objects. The Python Programming FAQ applies the same idea to function calls with the sentence “Remember that arguments are passed by assignment in Python.” Inside a function, the parameter name is bound to the object the caller passed in, so changes made to a mutable object are visible to the caller.
Two consequences explain most of the mistakes below. First, two names can refer to one mutable object, and changing the object through either name changes it for both. Second, rebinding a name changes only that name’s binding, leaving other names untouched. Whether a line of code mutates an object or rebinds a name is the detail that decides the outcome.
Mistakes with shared objects
1. Assuming assignment copies a list
This is the most common surprise for newcomers coming from languages where assignment copies values:
Free tools Windows power users keep installed
One-click scans. No signup required.
#1 Best Overall
a = [1, 2, 3]
b = a
b.append(4)
print(a) # [1, 2, 3, 4]
print(a is b) # True
Both names point to the same list, so append changes the one object they share. When you need independent state, make a copy. For a list, b = a.copy(), b = list(a), and b = a[:] each create a new top-level list. The copy is shallow. Nested objects inside the list are still shared:
nested = [[1], [2]]
clone = nested.copy()
clone[0].append(9)
print(nested) # [[1, 9], [2]]
If the structure contains mutable objects that must be independent, you need a deeper copy. The standard library’s copy module provides copy.deepcopy() for that case.
2. Confusing rebinding with mutation
Operators that look alike can behave differently. For lists, += extends the existing object in place, while + builds a new list. The effect is visible when another name shares the object:
a = [1, 2]
b = a
b += [3] # extends the shared list in place
print(a) # [1, 2, 3]
b = b + [4] # builds a new list and rebinds b only
print(a, b) # [1, 2, 3] [1, 2, 3, 4]
| Operation (lists) | Mutates the shared object? | Effect on other names pointing to the original list |
|---|---|---|
a.append(4) |
Yes | Visible through every name |
a += [4] |
Yes, the list is extended in place | Visible through every name |
a = a + [4] |
No, a new list is created and a is rebound |
Other names keep the old list |
b = a.copy() |
No, a new top-level list is created | Later changes to b do not affect a, though nested objects remain shared |
For immutable types such as integers and strings, += always produces a new object and rebinds the name. The mutation-versus-rebinding distinction matters mainly for mutable types.
3. Using a mutable default argument as per-call storage
Default values are evaluated once, when the def statement runs, not each time the function is called. A mutable default is therefore shared across every call:
Rank #2
def add_item(item, items=[]):
items.append(item)
return items
print(add_item("a")) # ['a']
print(add_item("b")) # ['a', 'b'] -- state carried over from the first call
The fix is a None sentinel, with the fresh mutable object created inside the function:
def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return items
print(add_item("a")) # ['a']
print(add_item("b")) # ['b']
Use this pattern when each call should start clean. If you intentionally want a shared cache, make that explicit with a clearly named module-level object instead of a hidden default.
Mistakes with scope
4. Expecting a function assignment to update a global variable
total = 0
def set_total():
total = 10 # creates a local name; the global is untouched
set_total()
print(total) # 0
Any assignment to a name inside a function makes that name local to the function, unless a global or nonlocal declaration says otherwise. The usual fix is to return the value and assign it at the call site:
def compute_total():
return 10
total = compute_total()
print(total) # 10
If module-level state is truly intended, declare it with global total inside the function. Returning values is usually easier to test and reason about, and the Python Programming FAQ recommends returning results as a clear option for output-style functions.
5. Reading a local name before it is assigned
count = 0
def bump():
print(count) # raises UnboundLocalError
count += 1
bump()
The function contains an assignment to count, so Python treats count as local throughout the entire function body. The read on the first line looks up a local that has no value yet, which raises UnboundLocalError. The exact message text varies between Python versions, but the exception type is the same. The reference for this behavior is in the Python execution model documentation.
Two fixes are common. Pass the value in and return the new one:
def bump(count):
return count + 1
count = bump(count)
Or declare the intended outer binding with global count, if the function really should change module state.
6. Using global or nonlocal without knowing which binding changes
The two keywords look similar but target different scopes. global refers to the module’s global namespace. nonlocal refers to a name in the nearest enclosing function scope, and it cannot point at a module-level name. The simple statements reference covers assignment and its effect on names:
def make_counter():
count = 0
def increment():
nonlocal count
count += 1
return count
return increment
counter = make_counter()
print(counter(), counter()) # 1 2
Without nonlocal, the line count += 1 would make count local to increment and fail. Before reaching for either keyword, ask whether explicit parameters and return values would make the dependency visible. Often they do.
Mistakes with closures and comprehensions
7. Capturing a changing loop variable in a lambda or nested function
A closure looks up its free variables when it is called, not when it is created. Every lambda below therefore sees the final value of i:
funcs = []
for i in range(3):
funcs.append(lambda: i)
print([f() for f in funcs]) # [2, 2, 2]
Bind the current value as a default argument, which is evaluated at creation time:
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
funcs = []
for i in range(3):
funcs.append(lambda i=i: i)
print([f() for f in funcs]) # [0, 1, 2]
A small factory function achieves the same result and reads more clearly when the closure body is longer:
def make_printer(value):
return lambda: value
funcs = [make_printer(i) for i in range(3)]
print([f() for f in funcs]) # [0, 1, 2]
8. Assuming a comprehension variable behaves like a loop variable
In Python 3, the iteration variable of a comprehension stays inside the comprehension. The loop variable of a for statement does not. Mixing those two cases up leads to confusion about what a name holds afterward:
x = "before"
values = [x for x in range(3)]
print(x) # before
for x in range(3):
pass
print(x) # 2
Assignment expressions are the exception that needs attention. A name bound with := inside a comprehension is bound in the containing scope, as PEP 572 specifies:
squares = [last := n * n for n in range(4)]
print(last) # 9
The rule depends on the construct, so avoid generalizing from one case to another. Python 2 behaved differently for list comprehensions, which is why older code sometimes depends on leaked loop variables.
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Scan for outdated or missing drivers - takes under a minute3Clear out junk files and repair common Windows errorsBest Value
Mistakes that make names misleading
9. Shadowing a built-in name such as list
Python looks up names through local, enclosing, global, and built-in scopes. If you bind a built-in name in your own scope, your binding wins:
list = [1, 2, 3]
numbers = list("abc") # TypeError: 'list' object is not callable
The error message points at the call, not at the earlier assignment that caused it. Choose names such as values or items. If you find a shadowed built-in in old code, search for the name’s assignments first, not just the line that fails.
10. Reusing one variable for unrelated meanings or types
Python permits rebinding a name to a value of any type, so this is not a runtime error. It is a readability problem. A name that holds raw text, then a parsed dictionary, then a validated object forces the reader to track which meaning applies at each line:
data = read_file(path) # str
data = json.loads(data) # dict
data = validate(data) # still a dict, but now checked
Distinct names make each stage visible: raw_text, parsed, and config. The Hitchhiker’s Guide to Python offers general style guidance on this kind of repeated reassignment. Treat it as a maintainability practice rather than a language rule.
A checklist when a value changes unexpectedly
- Check whether two names refer to the same object.
a is breturnsTruewhen they do, andid(a)shows the underlying identity. - Look for the assignment that changed the value. Search the function for
=,+=,for,import, and:=. Any of these can make a name local. - Inspect mutable default arguments in the function signature.
- If a lambda or nested function returns an unexpected value, check whether it captures a loop variable.
- If you get
UnboundLocalError, find the assignment to that name in the same function and decide whether it should be a parameter or an intentional global.
Most of these checks reduce to one question: which object does this name refer to right now, and which lines change that object or the name’s binding?
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




