No matches yet. Try another term or topic.
CollectionsFULL GUIDE · 8 MIN READ
Deleting keys while looping over a dictionary invalidates its live iterator. Use a snapshot of keys for in-place deletion or build a new filtered dictionary, then verify the original-state rule.
Strings & textFULL GUIDE · 6 MIN READ
Your output contains {name} instead of the value of name. Diagnose the missing f prefix, then learn when doubled braces are actually needed.
CollectionsFULL GUIDE · 8 MIN READ
Use a list comprehension when you need a list immediately. filter() applies a predicate lazily, so convert its iterator to a list only when you need the whole result.
Functions & classesFULL GUIDE · 8 MIN READ
A function assigns to a name, so Python treats it as local throughout that function; reading it before a value is assigned raises UnboundLocalError. Pass the current value in and return the update, or initialize a branch-dependent local before reading it.
Functions & classesFULL GUIDE · 8 MIN READ
Use isinstance(value, Base) when instances of Base and its subclasses are valid. Use an exact type check only when the contract intentionally excludes subclasses.
CollectionsFULL GUIDE · 6 MIN READ
The item was added, yet the variable assigned from append() contains None. See exactly which object changed and choose the right pattern for your program.
Functions & classesFULL GUIDE · 8 MIN READ
A default list is created when the function is defined, so later calls can reuse it. Trace that shared object, then create a fresh list when the caller omits the argument.
Files & setupFULL GUIDE · 9 MIN READ
A datetime without timezone information cannot be ordered against one with an offset. Identify what the naive value means, make that interpretation explicit, and verify comparisons across offsets.
CollectionsFULL GUIDE · 8 MIN READ
Square brackets fail when the value before them is None. Trace the lookup that produced None, handle the missing record explicitly, and test the edge cases.
Basics & inputFULL GUIDE · 8 MIN READ
The right-hand side produces more values than the assignment has targets. Count the fields, then choose the targets or a starred remainder that fits the data contract.
CollectionsFULL GUIDE · 8 MIN READ
A list cannot be a set member or dictionary key because it is mutable and unhashable. Convert fixed coordinate values to tuples, then test duplicates and nested values.
Basics & inputFULL GUIDE · 8 MIN READ
int() can parse a valid whole-number string, but text such as '12.5' is not an integer literal. Inspect the actual input, validate the intended format, and test the edge cases.
Logic & loopsFULL GUIDE · 6 MIN READ
Dividing by zero stops a calculation. Trace where the divisor came from, decide what zero means for your program, and test that rule instead of hiding the error.
Basics & inputFULL GUIDE · 5 MIN READ
The characters a person types look like a number, but input() returns a string. Find the mismatched types, convert deliberately, and handle text that cannot be converted.
Files & setupFULL GUIDE · 6 MIN READ
An install can succeed in one Python environment while your program runs in another. Prove which interpreter each command uses, then make installation and execution use the same one.
Logic & loopsFULL GUIDE · 5 MIN READ
If each loop iteration assigns a new value to one variable, the earlier values disappear. Trace the variable, create a collection before the loop, and add each result to it.
Files & setupFULL GUIDE · 6 MIN READ
A relative filename is resolved from the process working directory, which may differ from the script's folder. Print both paths, then choose an explicit location for the data file.
CollectionsFULL GUIDE · 6 MIN READ
A list has fewer positions than your code expects. Reproduce the failure, inspect the list length, and choose a safe lookup or iteration pattern that matches the task.
Functions & classesFULL GUIDE · 6 MIN READ
A message on the screen is not a value returned to the caller. Trace the function's result, add an explicit return, and test how another line can use that value.
CollectionsFULL GUIDE · 6 MIN READ
A dictionary lookup with square brackets requires the exact key to exist. Inspect the available keys, then decide whether a missing key should be handled or treated as an error.
CollectionsFULL GUIDE · 8 MIN READ
sorted(products) cannot compare whole dictionaries. Reproduce the TypeError, select a numeric price key, and test ties without changing the original list.
Basics & inputFrom Python History
Assuming the name means snake
The code runs, but the historical claim is false.
See the fix
COMMON MISTAKEprint("Python was named after a snake.")
FIXprint("Python was named after Monty Python.")
A program can run successfully and still print incorrect information. Check the fact as well as the syntax.
Basics & inputFrom Python History
Mixing up 1991 and 2008
The code runs, but it confuses two milestones.
See the fix
COMMON MISTAKEprint("Python 3.0 was first shared in 1991.")
FIXprint("Python was public in 1991; Python 3.0 arrived in 2008.")
Python's first public sharing and the Python 3.0 release happened at different times.
Basics & inputFrom Hello, World!
Unquoted text
NameError: Python looks for a variable named Hello.
See the fix
COMMON MISTAKEprint(Hello)
Put literal text inside quotes so Python treats it as a string.
Basics & inputFrom Hello, World!
Missing parentheses
SyntaxError in Python 3.
See the fix
COMMON MISTAKEprint "Hello"
print is a function, so its argument goes inside parentheses.
Basics & inputFrom Variables and Types
Case-sensitive names
NameError because Name and name are different names.
See the fix
COMMON MISTAKEname = "Maya"
print(Name)
FIXname = "Maya"
print(name)
Use exactly the same spelling and capitalization each time.
Basics & inputFrom Variables and Types
Mixing text and numbers
TypeError when joining a string and an integer.
See the fix
COMMON MISTAKEage = 12
print("Age: " + age)
FIXage = 12
print("Age: " + str(age))
Convert the number to a string before concatenating it with text.
CollectionsFrom Lists
Index past the end
IndexError because the only valid positive indexes are 0 and 1.
See the fix
COMMON MISTAKEitems = ["a", "b"]
print(items[2])
FIXitems = ["a", "b"]
print(items[1])
The final positive index is len(items) - 1.
CollectionsFrom Lists
Saving append's return value
The variable becomes None.
See the fix
COMMON MISTAKEitems = [1, 2]
items = items.append(3)
print(items)
FIXitems = [1, 2]
items.append(3)
print(items)
Call append without assigning its return value.
Logic & loopsFrom Basic Operators
Using ^ for powers
It prints 1 because ^ is bitwise XOR.
See the fix
COMMON MISTAKEprint(2 ^ 3)
Python's exponentiation operator is **.
Logic & loopsFrom Basic Operators
Dividing by zero
ZeroDivisionError.
See the fix
COMMON MISTAKEprint(10 / 0)
The divisor must be nonzero.
Strings & textFrom String Formatting
Missing f prefix
The literal text Hi, {name} is displayed.
See the fix
COMMON MISTAKEname = "Maya"
print("Hi, {name}")
FIXname = "Maya"
print(f"Hi, {name}")
Add f before the opening quote to evaluate placeholders.
Strings & textFrom String Formatting
Unknown placeholder
NameError because username has not been defined.
See the fix
COMMON MISTAKEname = "Maya"
print(f"Hi, {username}")
FIXname = "Maya"
print(f"Hi, {name}")
A placeholder must refer to a defined name or valid expression.
Strings & textFrom Basic String Operations
Index outside a string
IndexError because valid positive indexes are 0, 1, and 2.
See the fix
COMMON MISTAKEword = "cat"
print(word[3])
FIXword = "cat"
print(word[2])
The last positive index is one less than the string length.
Strings & textFrom Basic String Operations
Changing a character in place
TypeError because strings cannot be changed in place.
See the fix
COMMON MISTAKEword = "cat"
word[0] = "b"
FIXword = "cat"
word = "b" + word[1:]
print(word)
Construct a new string and assign it to the variable.
Logic & loopsFrom Conditions
Missing colon
SyntaxError on the if line.
See the fix
COMMON MISTAKEscore = 75
if score >= 60
print("Passed")
FIXscore = 75
if score >= 60:
print("Passed")
End the if header with a colon.
Logic & loopsFrom Conditions
Missing indentation
IndentationError after the if header.
See the fix
COMMON MISTAKEscore = 75
if score >= 60:
print("Passed")
FIXscore = 75
if score >= 60:
print("Passed")
Indent statements that belong to the branch.
Logic & loopsFrom Loops
A while loop never progresses
The condition stays true, so the loop keeps printing 0.
See the fix
COMMON MISTAKEcount = 0
while count < 3:
print(count)
FIXcount = 0
while count < 3:
print(count)
count += 1
Update the value tested by the condition.
Logic & loopsFrom Loops
Expecting the stop value
Only 1, 2, and 3 appear.
See the fix
COMMON MISTAKEfor n in range(1, 4):
print(n)
# Expecting 1, 2, 3, 4
FIXfor n in range(1, 5):
print(n)
range excludes its stop argument.
Functions & classesFrom Functions
Printing instead of returning
The function prints 8, but result is None.
See the fix
COMMON MISTAKEdef double(value):
print(value * 2)
result = double(4)
print(result)
FIXdef double(value):
return value * 2
result = double(4)
print(result)
Use return when callers need to store or reuse the calculated value.
Functions & classesFrom Functions
Missing required argument
TypeError because name was not supplied.
See the fix
COMMON MISTAKEdef greet(name):
print(f"Hi, {name}")
greet()
FIXdef greet(name):
print(f"Hi, {name}")
greet("Maya")
Provide one argument for each required parameter.
Functions & classesFrom Classes and Objects
Omitting self in a method
TypeError because Python passes the instance automatically.
See the fix
COMMON MISTAKEclass Book:
def summary():
return "A book"
book = Book()
print(book.summary())
FIXclass Book:
def summary(self):
return "A book"
book = Book()
print(book.summary())
Instance methods need self as their first parameter.
Functions & classesFrom Classes and Objects
Forgetting self on an attribute
AttributeError because no title attribute was stored on the object.
See the fix
COMMON MISTAKEclass Book:
def __init__(self, title):
title = title
book = Book("Python Notes")
print(book.title)
FIXclass Book:
def __init__(self, title):
self.title = title
book = Book("Python Notes")
print(book.title)
Assign to self.title to save the value on the instance.
CollectionsFrom Dictionaries
Looking up a missing key
KeyError for score.
See the fix
COMMON MISTAKElearner = {"name": "Maya"}
print(learner["score"])
FIXlearner = {"name": "Maya"}
print(learner.get("score", 0))
Use get with a fallback if the key may be absent.
CollectionsFrom Dictionaries
Using a list-style index
KeyError because 0 is not a key.
See the fix
COMMON MISTAKElearner = {"name": "Maya"}
print(learner[0])
FIXlearner = {"name": "Maya"}
print(learner["name"])
Dictionary lookup uses keys, not numeric positions.
Files & setupFrom Modules and Packages
Using a module before importing it
NameError if math has not been imported in this run.
See the fix
COMMON MISTAKEprint(math.sqrt(9))
FIXimport math
print(math.sqrt(9))
Import the module before using its name.
Files & setupFrom Modules and Packages
Shadowing the module name
AttributeError because math now refers to the integer 4.
See the fix
COMMON MISTAKEimport math
math = 4
print(math.sqrt(9))
FIXimport math
number = 4
print(math.sqrt(9))
Avoid reusing an imported module's name for another value.
Basics & inputFrom Input and Output
Adding a number to input text
TypeError because entered_age is text.
See the fix
COMMON MISTAKEentered_age = "17"
print(entered_age + 1)
FIXentered_age = "17"
print(int(entered_age) + 1)
Convert numeric text before arithmetic.
Basics & inputFrom Input and Output
Converting nonnumeric text
ValueError because the text is not a valid integer.
See the fix
COMMON MISTAKEentered_age = "seventeen"
print(int(entered_age))
FIXentered_age = "17"
print(int(entered_age))
int accepts text that represents a valid whole number.
ProjectsFrom Project: Tip Calculator
Using the percent as a multiplier
A 20% tip on 40 becomes 800 instead of 8.
See the fix
COMMON MISTAKEtip = bill * 20
Convert a percent to a fraction before multiplying.
ProjectsFrom Project: Tip Calculator
Dividing before adding the tip
The whole tip is charged to every person.
See the fix
COMMON MISTAKEeach = bill / people + tip
FIXeach = (bill + tip) / people
Add the tip once to the whole bill, then split the total.
ProjectsFrom Project: Number Guessing Game
Assigning instead of comparing
SyntaxError because = is assignment, not comparison.
See the fix
COMMON MISTAKEif guess = secret:
print('Correct')
FIXif guess == secret:
print('Correct')
Use == when asking whether two values are equal.
ProjectsFrom Project: Number Guessing Game
Reversing the low hint
The player receives a misleading hint.
See the fix
COMMON MISTAKEif guess < secret:
return 'Too high'
FIXif guess < secret:
return 'Too low'
Compare the guessed number to the secret from the player's perspective.
ProjectsFrom Project: Text Analyzer
Counting the text's characters
'red blue' reports eight characters instead of two words.
See the fix
COMMON MISTAKEword_count = len(text)
FIXword_count = len(text.split())
Split the text first when you want a word count.
ProjectsFrom Project: Text Analyzer
Treating capitalization as a new word
'Code' and 'code' become separate dictionary keys.
See the fix
COMMON MISTAKEwords = text.split()
FIXwords = text.lower().split()
Normalize case before counting for a case-insensitive report.
CollectionsFrom Tuples and Sets
Expecting a set to preserve duplicates
It prints False because a set has only two distinct values.
See the fix
COMMON MISTAKEprint(len(set([1, 1, 2])) == 3)
FIXprint(len(set([1, 1, 2])) == 2)
A set keeps one copy of each equal value.
CollectionsFrom Tuples and Sets
Using {} for an empty set
AttributeError: a dictionary has no add method.
See the fix
COMMON MISTAKEitems = {}
items.add('python')
FIXitems = set()
items.add('python')
Construct an empty set with set().
Files & setupFrom Exceptions
Catching the wrong exception
ZeroDivisionError still stops the program.
See the fix
COMMON MISTAKEtry:
print(4 / 0)
except ValueError:
print('Bad number')
FIXtry:
print(4 / 0)
except ZeroDivisionError:
print('Cannot divide by zero')
The except type must match the error that may be raised.
Files & setupFrom Exceptions
Using a bare except
Unrelated bugs are hidden behind a vague message.
See the fix
COMMON MISTAKEtry:
risky_code()
except:
print('Something failed')
FIXtry:
number = float('bad')
except ValueError:
print('Invalid number')
Catch an expected error by name so other failures remain visible.
Files & setupFrom Reading and Writing Files
Reading before writing
FileNotFoundError if the virtual file has not been created in this run.
See the fix
COMMON MISTAKEwith open('new_note.txt', 'r', encoding='utf-8') as file:
print(file.read())
FIXwith open('new_note.txt', 'w', encoding='utf-8') as file:
file.write('Ready')
with open('new_note.txt', 'r', encoding='utf-8') as file:
print(file.read())
Create the file before trying to read it in a fresh exercise run.
Files & setupFrom Reading and Writing Files
Expecting append from write mode
The old file contents disappear.
See the fix
COMMON MISTAKEwith open('note.txt', 'w', encoding='utf-8') as file:
file.write('second')
FIXwith open('note.txt', 'a', encoding='utf-8') as file:
file.write('second')
Choose a only when the new text should be added at the end.
Files & setupFrom Installing Python and Virtual Environments
Typing a terminal command into Python code
SyntaxError in the browser editor: this is a shell command.
See the fix
COMMON MISTAKEpython -m venv .venv
FIXimport sys
print(sys.version_info.major)
Use the browser editor for Python statements and a local terminal for setup commands.
Files & setupFrom Installing Python and Virtual Environments
Assuming a browser version is the local version
The claim may be false because the browser runtime is separate.
See the fix
COMMON MISTAKEprint('My laptop uses Python 3.13')
FIXimport sys
print(sys.version_info.major, sys.version_info.minor)
Inspect each interpreter in its own environment rather than assuming they match.
CollectionsFrom List Comprehensions
Filtering odd values by accident
The result contains doubled odd values, such as [2, 6] for [1, 2, 3, 4].
See the fix
COMMON MISTAKE[n * 2 for n in numbers if n % 2]
FIX[n * 2 for n in numbers if n % 2 == 0]
The remainder is 1 for odd numbers, which is truthy. Compare explicitly with zero for even values.
CollectionsFrom List Comprehensions
Putting the filter before the loop
SyntaxError: expected else after the conditional expression.
See the fix
COMMON MISTAKE[n * 2 if n % 2 == 0 for n in numbers]
FIX[n * 2 for n in numbers if n % 2 == 0]
A trailing if filters items. An if before for is a different expression form and needs an else branch.
CollectionsFrom List Comprehensions
Keeping unwanted values while trying to filter
Odd inputs become zero, so the output still has one element per input instead of only even values.
See the fix
COMMON MISTAKE[n if n % 2 == 0 else 0 for n in numbers]
FIX[n for n in numbers if n % 2 == 0]
Use a trailing if when items should disappear. Use an if/else expression when every item must produce an output.
Functions & classesFrom Function Arguments, Return Values, and Scope
Trying to read a local variable outside its function
NameError: name 'total' is not defined.
See the fix
COMMON MISTAKEdef total_with_tax(price):
total = price * 1.2
total_with_tax(20)
print(total)
FIXdef total_with_tax(price):
total = price * 1.2
return total
print(total_with_tax(20))
Return the local value. The call expression then provides it to the caller.
Functions & classesFrom Function Arguments, Return Values, and Scope
Printing instead of returning
The output contains 8 and then None.
See the fix
COMMON MISTAKEdef double(n):
print(n * 2)
result = double(4)
print(result)
FIXdef double(n):
return n * 2
result = double(4)
print(result)
Printing is a side effect. A return statement makes the value available for assignment and further calculation.
Files & setupFrom Read and Write JSON and CSV
Passing a Python repr as JSON
JSONDecodeError because JSON keys and strings need double quotes.
See the fix
COMMON MISTAKEjson.loads("{'name': 'Ada'}")
FIXjson.loads('{"name": "Ada"}')
Use json.dumps to produce JSON from Python values instead of assembling a repr-like string yourself.
Files & setupFrom Read and Write JSON and CSV
Adding a CSV number as text
TypeError: can only concatenate str (not int) to str.
See the fix
COMMON MISTAKErow = next(csv.DictReader(StringIO('score\n7\n')))
print(row['score'] + 1)
FIXrow = next(csv.DictReader(StringIO('score\n7\n')))
print(int(row['score']) + 1)
Every CSV cell starts as text. Convert with int only when the data should contain a whole number.
CollectionsFrom Loop with enumerate() and zip()
Treating enumerate's first value as the item
AttributeError because topic is the integer index.
See the fix
COMMON MISTAKEfor topic, number in enumerate(['loops'], start=1):
print(topic.upper())
FIXfor number, topic in enumerate(['loops'], start=1):
print(topic.upper())
enumerate gives index first, item second. Name the unpacked values in that order.
CollectionsFrom Loop with enumerate() and zip()
Silently dropping an extra score
The second score is lost and no exception explains the mismatch.
See the fix
COMMON MISTAKElist(zip(['Ada'], [8, 5]))
FIXlist(zip(['Ada'], [8, 5], strict=True))
Use strict=True when both sequences must have matching lengths; consume the iterator to trigger the check.
CollectionsFrom Dictionary and Set Comprehensions
Using braces without a colon for a mapping
The result is a set of words with no lengths.
See the fix
COMMON MISTAKElengths = {word for word in ['cat', 'go']}
FIXlengths = {word: len(word) for word in ['cat', 'go']}
Include key: value before for when the result should be a dictionary.
CollectionsFrom Dictionary and Set Comprehensions
Assuming braces make an empty set
AttributeError: 'dict' object has no attribute 'add'.
See the fix
COMMON MISTAKEseen = {}
seen.add('Ada')
FIXseen = set()
seen.add('Ada')
Python reserves empty braces for a dictionary. Use set() for an empty set.
Functions & classesFrom Test Python Functions with Assertions
Printing a result without checking it
The terminal shows a value, but a wrong value does not fail a repeatable test.
See the fix
COMMON MISTAKEprint(double(4))
State the expected value explicitly so a regression fails when the test runs.
Functions & classesFrom Test Python Functions with Assertions
Testing only the easiest case
An implementation that always returns value passes even though it fails outside the range.
See the fix
COMMON MISTAKEassert clamp(5, 0, 10) == 5
FIXassert clamp(5, 0, 10) == 5
assert clamp(-1, 0, 10) == 0
assert clamp(11, 0, 10) == 10
Choose inputs that exercise each branch, including boundaries and errors.
Functions & classesFrom Test Python Functions with Assertions
Using assert to validate user input
Running Python with -O can remove the check and allow a negative age through.
See the fix
COMMON MISTAKEdef save_age(age):
assert age >= 0
return age
FIXdef save_age(age):
if age < 0:
raise ValueError('age must be non-negative')
return age
Explicit validation runs regardless of optimization; reserve assertions for checks whose removal does not change required behavior.
Files & setupFrom Dates and Timedeltas in Python
Editing the day characters directly
It prints an invalid February 30 instead of crossing into March.
See the fix
COMMON MISTAKEstart = '2024-02-28'
print(start[:-2] + str(int(start[-2:]) + 2))
FIXstart = date.fromisoformat('2024-02-28')
print((start + timedelta(days=2)).isoformat())
Calendar arithmetic handles the length of each month and leap years.
Files & setupFrom Dates and Timedeltas in Python
Trying to add a number directly to a date
TypeError because a date cannot be added to a bare integer.
See the fix
COMMON MISTAKEdate.fromisoformat('2024-02-28') + 1
FIXdate.fromisoformat('2024-02-28') + timedelta(days=1)
Wrap the offset in a timedelta to state its unit and meaning.
ProjectsFrom Project: CSV Expense Tracker
Adding amount strings
The result is the text 12.503.00, not a money total.
See the fix
COMMON MISTAKEtotal = "12.50" + "3.00"
FIXtotal = Decimal("12.50") + Decimal("3.00")
Convert each string to Decimal before adding it. Build Decimal from the source text to keep decimal values exact.
ProjectsFrom Project: CSV Expense Tracker
Splitting a CSV row on commas
A quoted category such as Food, snacks is split into too many pieces.
See the fix
COMMON MISTAKEcategory, amount = line.split(",")
FIXfor row in csv.DictReader(StringIO(csv_text)):
category = row["category"]
The csv module understands quoted fields and uses the header names.
ProjectsFrom Project: CSV Expense Tracker
Replacing an earlier category total
A second Food row hides the first Food expense.
See the fix
COMMON MISTAKEtotals[category] = amount
FIXtotals[category] = totals.get(category, Decimal("0")) + amount
Use the previous total as the starting point when a category appears again.
ProjectsFrom Project: Contact Book
Normalizing names only when saving
Searching for ADA LOVELACE misses an existing contact.
See the fix
COMMON MISTAKEbook[name.strip().casefold()] = record
found = book.get(search_name)
FIXbook[name.strip().casefold()] = record
found = book.get(search_name.strip().casefold())
Apply the same rule to both the saved and searched name.
ProjectsFrom Project: Contact Book
Indexing a missing contact directly
The program raises KeyError instead of reporting a missing contact.
See the fix
COMMON MISTAKErecord = book["missing person"]
FIXrecord = book.get("missing person")
get returns None for an absent key, allowing the caller to show a friendly result.
ProjectsFrom Project: Contact Book
Assuming names identify real people uniquely
Adding a second person with the same name overwrites the first.
See the fix
COMMON MISTAKEbook[person_name] = email # two people can share this name
FIX# For real data, use a separate stable contact ID as the key.
Normalized names are convenient for this exercise but cannot serve as reliable unique identifiers in a real address book.
CollectionsFrom Sort Python Lists and Dictionaries with key
Trying to compare whole dictionaries
TypeError: '<' is not supported between instances of 'dict' and 'dict'.
See the fix
COMMON MISTAKEsorted([{'price': 12}, {'price': 3}])
FIXsorted([{'price': 12}, {'price': 3}], key=lambda item: item['price'])
Choose the numeric field that defines the order; dictionaries themselves have no default less-than ordering.
CollectionsFrom Sort Python Lists and Dictionaries with key
Assigning the result of list.sort()
The call prints None, and products was changed in place.
See the fix
COMMON MISTAKEproducts = [{'price': 12}, {'price': 3}]
ordered = products.sort(key=lambda item: item['price'])
print(ordered)
FIXordered = sorted(products, key=lambda item: item['price'])
Use sorted() when you need a new list and must preserve the input.
CollectionsFrom Sort Python Lists and Dictionaries with key
Sorting numeric prices as text
The text '10' comes before '2' lexicographically.
See the fix
COMMON MISTAKEsorted(['10', '2'])
FIXsorted(['10', '2'], key=int)
Convert numeric text in the key function when numeric order is intended.
Files & setupFrom Work with File Paths using pathlib
Writing before the parent folder exists
FileNotFoundError if notes has not been created.
See the fix
COMMON MISTAKEPath('notes/day-1.txt').write_text('hello', encoding='utf-8')
FIXpath = Path('notes/day-1.txt')
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text('hello', encoding='utf-8')
Create the containing folder first; parents=True also creates missing levels above it.
Files & setupFrom Work with File Paths using pathlib
Assuming write_text appends
The file contains only second.
See the fix
COMMON MISTAKEpath.write_text('first', encoding='utf-8')
path.write_text('second', encoding='utf-8')
FIXpath.write_text('first', encoding='utf-8')
with path.open('a', encoding='utf-8') as file:
file.write('second')
Use append mode explicitly when existing text must be kept.
Files & setupFrom Work with File Paths using pathlib
Joining path strings without a separator
The result is reportssummary.txt rather than a child file.
See the fix
COMMON MISTAKE'reports' + 'summary.txt'
FIXPath('reports') / 'summary.txt'
The / operator on Path joins components using the current system's path rules.
Strings & textFrom Python Regular Expressions: search and findall
Using a non-raw boundary pattern
The Python string contains backspace characters rather than regex word-boundary escapes, so the expected code is missed.
See the fix
COMMON MISTAKEre.findall("\bORD-[0-9]{4}\b", "ORD-1234")
FIXre.findall(r"\bORD-[0-9]{4}\b", "ORD-1234")
Prefix a regex pattern with r so backslashes reach the regular-expression parser as intended.
Strings & textFrom Python Regular Expressions: search and findall
Accepting only the start of an invalid code
The expression is True even though a fifth digit remains.
See the fix
COMMON MISTAKEre.match(r'ORD-[0-9]{4}', 'ORD-12345') is not None
FIXre.fullmatch(r'ORD-[0-9]{4}', 'ORD-12345') is not None
Use fullmatch to validate a whole string; use word boundaries when extracting a whole code from a sentence.
Strings & textFrom Python Regular Expressions: search and findall
Expecting search to return text directly
AttributeError because search returns a Match object, not a string.
See the fix
COMMON MISTAKEre.search(r'cat', 'A cat naps.').upper()
FIXmatch = re.search(r'cat', 'A cat naps.')
print(match.group().upper() if match else 'none')
Check for None, then call group() to get matched text.
Logic & loopsFrom Iterators and Generators in Python
Returning a list instead of yielding values
The values are correct, but the function returns a list rather than a generator.
See the fix
COMMON MISTAKEdef even_numbers(limit):
return [n for n in range(limit) if n % 2 == 0]
FIXdef even_numbers(limit):
for n in range(limit):
if n % 2 == 0:
yield n
Use yield when the task requires items to be produced on demand.
Logic & loopsFrom Iterators and Generators in Python
Reusing an already consumed generator
The second print displays [] because the first pass consumed values.
See the fix
COMMON MISTAKEvalues = (n * n for n in range(3))
print(list(values))
print(list(values))
FIXdef squares():
return (n * n for n in range(3))
print(list(squares()))
print(list(squares()))
Call the generator-producing function again to make a fresh iterator.
Logic & loopsFrom Iterators and Generators in Python
Calling next after exhaustion without a default
The second request raises StopIteration.
See the fix
COMMON MISTAKEitems = iter([1])
next(items)
next(items)
FIXitems = iter([1])
next(items)
print(next(items, None))
Use a default or a for loop if exhaustion is expected.
Functions & classesFrom Python dataclasses: Fields, Defaults, and asdict
Using a mutable list as a direct default
Modern Python raises ValueError at class creation because a mutable list is an unsafe default.
See the fix
COMMON MISTAKE@dataclass
class StudyTask:
title: str
tags: list[str] = []
FIX@dataclass
class StudyTask:
title: str
tags: list[str] = field(default_factory=list)
A factory supplies an independent list each time an instance is created.
Functions & classesFrom Python dataclasses: Fields, Defaults, and asdict
Putting a required field after a default
TypeError: non-default argument 'title' follows default argument.
See the fix
COMMON MISTAKE@dataclass
class StudyTask:
done: bool = False
title: str
FIX@dataclass
class StudyTask:
title: str
done: bool = False
Keep required fields before optional fields with defaults.
Functions & classesFrom Python dataclasses: Fields, Defaults, and asdict
Assuming an annotation validates data
The instance can still be created with an integer title.
See the fix
COMMON MISTAKEtask = StudyTask(123)
# Assume title must be a str at runtime.
FIXif not isinstance(title, str):
raise TypeError('title must be text')
task = StudyTask(title)
Use explicit validation when a runtime guarantee matters; an annotation alone is descriptive.
CollectionsFrom Count and Group Data with Counter and defaultdict
Passing a list instance as the default factory
TypeError: first argument must be callable or None.
See the fix
COMMON MISTAKEgroups = defaultdict(list())
FIXgroups = defaultdict(list)
Pass the constructor itself so defaultdict can call it for each missing key.
CollectionsFrom Count and Group Data with Counter and defaultdict
Treating Counter as text normalization
Bug and bug are counted as two different labels.
See the fix
COMMON MISTAKECounter(['Bug', 'bug'])
FIXCounter(label.strip().lower() for label in ['Bug', 'bug'])
Normalize the data before counting when case and surrounding whitespace should not matter.
CollectionsFrom Count and Group Data with Counter and defaultdict
Reading a missing group by indexing
The lookup silently adds unknown as an empty group.
See the fix
COMMON MISTAKEgroups = defaultdict(list)
print(groups['unknown'])
FIXprint(groups.get('unknown', []))
Use get for a read that should leave the mapping unchanged.
Functions & classesFrom Write Clearer Functions with Python Type Hints
Claiming a possibly absent result is always a string
The function can return None although its annotation promises str.
See the fix
COMMON MISTAKEdef lookup(names: list[str], wanted: str) -> str:
return next((name for name in names if name == wanted), None)
FIXdef lookup(names: list[str], wanted: str) -> str | None:
return next((name for name in names if name == wanted), None)
Include None in the return hint whenever a normal branch can return it.
Functions & classesFrom Write Clearer Functions with Python Type Hints
Expecting an annotation to validate outside input
A runtime TypeError occurs inside the function; the hint did not convert '20'.
See the fix
COMMON MISTAKEdef age_next_year(age: int) -> int:
return age + 1
age_next_year('20')
FIXage = int('20')
print(age_next_year(age))
Convert or validate user input explicitly before calling a function that expects an integer.
Functions & classesFrom Write Clearer Functions with Python Type Hints
Using a bare container hint
Readers cannot tell what the list contains or what maps to each count.
See the fix
COMMON MISTAKEdef count_words(words: list) -> dict:
FIXdef count_words(words: list[str]) -> dict[str, int]:
Element, key, and value hints make the contract more useful.
Functions & classesFrom Write Python Decorators that Preserve Behavior
Forgetting to return the wrapper
The decorated name becomes None, so calling it raises TypeError.
See the fix
COMMON MISTAKEdef announce(func):
def wrapper():
return func()
# no return wrapper
FIXdef announce(func):
def wrapper():
return func()
return wrapper
The decorator itself must return the callable that replaces the original name.
Functions & classesFrom Write Python Decorators that Preserve Behavior
Discarding the original result
A function that returned a value now appears to return None.
See the fix
COMMON MISTAKEdef wrapper(*args, **kwargs):
func(*args, **kwargs)
FIXdef wrapper(*args, **kwargs):
return func(*args, **kwargs)
Return the original result unless changing it is the deliberate purpose of the decorator.
Functions & classesFrom Write Python Decorators that Preserve Behavior
Losing the decorated function's name
The decorated function reports __name__ as wrapper.
See the fix
COMMON MISTAKEdef wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
FIX@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
functools.wraps keeps useful metadata and exposes __wrapped__.
Files & setupFrom Manage Resources with Python with Statements
Opening a file without a bounded lifetime
The handle may remain open if later code raises before close().
See the fix
COMMON MISTAKEnote = open('note.txt', 'w', encoding='utf-8')
note.write('draft')
FIXwith open('note.txt', 'w', encoding='utf-8') as note:
note.write('draft')
with asks the file's context manager to close the handle on either exit path.
Files & setupFrom Manage Resources with Python with Statements
Forgetting finally in a generator manager
If the with body raises, the line after yield is skipped and cleanup is lost.
See the fix
COMMON MISTAKE@contextmanager
def managed(log):
log.append('open')
yield
log.append('close')
FIX@contextmanager
def managed(log):
log.append('open')
try:
yield
finally:
log.append('close')
Put cleanup in finally so it runs during exception propagation.
Files & setupFrom Manage Resources with Python with Statements
Silencing every failure as cleanup
A real write error can disappear without the caller noticing.
See the fix
COMMON MISTAKEwith suppress(Exception):
write_important_note()
FIXwith note_writer('note.txt') as note:
note.write('important')
Resource cleanup and error handling are separate decisions; allow unexpected errors to propagate.
Logic & loopsFrom Build Small Data Pipelines with itertools
Materializing everything before taking a few values
The entire readings iterator is consumed, even when only three totals are needed.
See the fix
COMMON MISTAKEfirst = list(accumulate(readings))[:3]
FIXfirst = list(islice(accumulate(readings), 3))
Place islice before list so upstream work stops at the requested count.
Logic & loopsFrom Build Small Data Pipelines with itertools
Treating groupby as global grouping
There are two separate ok groups, not one combined group.
See the fix
COMMON MISTAKEfrom itertools import groupby
print([(key, list(group)) for key, group in groupby(['ok', 'fail', 'ok'])])
FIXfrom itertools import groupby
statuses = ['ok', 'fail', 'ok']
ordered = sorted(statuses)
print([(key, list(group)) for key, group in groupby(ordered)])
Sort first when you need one group per key, and consider whether sorting is acceptable for your data.
Logic & loopsFrom Build Small Data Pipelines with itertools
Trying to reuse an exhausted pipeline
The second list is empty because the iterator has already been consumed.
See the fix
COMMON MISTAKEstream = chain([1], [2])
print(list(stream))
print(list(stream))
FIXstream = chain([1], [2])
values = list(stream)
print(values)
print(values)
Recreate an iterator for another pass or store the results if repeated access is required.
Functions & classesFrom Understand Recursion and the Call Stack
Recursing on the unchanged input
The input never shrinks, so calls end in RecursionError.
See the fix
COMMON MISTAKEdef length(items):
if not items: return 0
return 1 + length(items)
FIXdef length(items):
if not items: return 0
return 1 + length(items[1:])
Each recursive call must move closer to the empty-list base case.
Functions & classesFrom Understand Recursion and the Call Stack
Ignoring the child's return value
Numbers inside nested lists never contribute to the parent total.
See the fix
COMMON MISTAKEif isinstance(item, list):
sum_nested(item)
FIXif isinstance(item, list):
total += sum_nested(item)
The parent must use the value returned by its child call.
Functions & classesFrom Understand Recursion and the Call Stack
Sharing an accumulator between calls
A second call starts from the first call's answer and nested lists are not handled.
See the fix
COMMON MISTAKEtotal = 0
def sum_nested(items):
global total
for item in items: total += item
return total
FIXdef sum_nested(items):
total = 0
for item in items:
total += sum_nested(item) if isinstance(item, list) else item
return total
A local total belongs to each call and is combined by returned values.
Functions & classesFrom Read Structured Data with match and case
Using a capture where a literal is required
message captures any value, so this branch matches everything.
See the fix
COMMON MISTAKEmatch event_type:
case message:
return 'message'
FIXmatch event_type:
case 'message':
return 'message'
Quoted text is a literal pattern; an unqualified bare name captures.
Functions & classesFrom Read Structured Data with match and case
Placing a broad branch too early
The origin is handled by the first two-item pattern and never reaches its specific branch.
See the fix
COMMON MISTAKEmatch point:
case (x, y): return 'point'
case (0, 0): return 'origin'
FIXmatch point:
case (0, 0): return 'origin'
case (x, y): return 'point'
Order specific shapes before general ones.
Functions & classesFrom Read Structured Data with match and case
Assuming a shape also means valid content
Whitespace-only messages are treated as meaningful content.
See the fix
COMMON MISTAKEcase {'type': 'message', 'text': str(text)}:
return 'Message: ' + text
FIXcase {'type': 'message', 'text': str(text)} if text.strip():
return 'Message: ' + text.strip()
A guard expresses conditions that a shape alone does not capture.
Files & setupFrom Replace Debug Prints with Python Logging
Expecting INFO to appear under a WARNING threshold
The INFO record is filtered out before it reaches a handler.
See the fix
COMMON MISTAKElogger.setLevel(logging.WARNING)
logger.info('started')
FIXlogger.setLevel(logging.INFO)
logger.info('started')
Choose a threshold at or below the messages you need.
Files & setupFrom Replace Debug Prints with Python Logging
Adding a new handler to a shared logger on every call
Repeated setup can make the same event appear multiple times.
See the fix
COMMON MISTAKElogger = logging.getLogger('app')
logger.addHandler(logging.StreamHandler(stream))
FIXlogger = logging.Logger('one-call')
logger.addHandler(logging.StreamHandler(stream))
Use a fresh logger for a self-contained task or manage and remove shared handlers explicitly.
Files & setupFrom Replace Debug Prints with Python Logging
Logging secrets while debugging
The token may be written to logs and retained outside the program.
See the fix
COMMON MISTAKElogger.info('token=%s', api_token)
FIXlogger.info('Authentication request started')
Log the event and context needed for diagnosis without credentials or personal details.
Functions & classesFrom Start Concurrent I/O with asyncio
Calling a coroutine without driving it
The result is a coroutine object rather than '1: ADA', and it may be reported as never awaited.
See the fix
COMMON MISTAKEresult = fetch_label(1, 'ada')
print(result)
FIXresult = asyncio.run(fetch_label(1, 'ada'))
print(result)
At a top-level script, asyncio.run executes the coroutine. Inside async code, use await instead.
Functions & classesFrom Start Concurrent I/O with asyncio
Awaiting independent waits one at a time
Independent waits are serialized, so their delays add up.
See the fix
COMMON MISTAKEresults = []
for item in jobs:
results.append(await fetch(item))
FIXresults = await asyncio.gather(*(fetch(item) for item in jobs))
gather schedules independent awaitables together; result order remains aligned with jobs.
Functions & classesFrom Start Concurrent I/O with asyncio
Starting another event loop inside async code
asyncio.run raises RuntimeError when an event loop is already running.
See the fix
COMMON MISTAKEasync def main():
return asyncio.run(fetch_label(1, 'ada'))
FIXasync def main():
return await fetch_label(1, 'ada')
Use await inside a coroutine and reserve asyncio.run for a synchronous top-level script.
Functions & classesFrom Start Concurrent I/O with asyncio
Gathering jobs that depend on each other
The profile job cannot receive a token that has not been returned yet.
See the fix
COMMON MISTAKEawait asyncio.gather(fetch_token(), fetch_profile(token))
FIXtoken = await fetch_token()
profile = await fetch_profile(token)
Start independent waits together; keep a real producer-consumer dependency in sequence.
ProjectsFrom Project: Analyze Application Logs
Splitting every pipe
A message containing a pipe creates four parts and unpacking raises ValueError.
See the fix
COMMON MISTAKEparts = line.split("|")
timestamp, level, message = parts
FIXparts = [part.strip() for part in line.split("|", 2)]
if len(parts) != 3 or not all(parts):
raise ValueError("Malformed log row")
timestamp, level, message = parts
Limit the split to two separators and validate the three fields before unpacking.
ProjectsFrom Project: Analyze Application Logs
Replacing a previous level count
The report says ERROR: 1 even after several ERROR records.
See the fix
COMMON MISTAKEcounts[level] = 1
FIXcounts[level] = counts.get(level, 0) + 1
Add one to the previous subtotal rather than assigning the initial value every time.
ProjectsFrom Project: Analyze Application Logs
Treating malformed rows as events
A row without a separator raises IndexError; an empty level can create a misleading category.
See the fix
COMMON MISTAKElevel = raw_line.split("|")[1].strip()
FIXparts = [part.strip() for part in raw_line.split("|", 2)]
if len(parts) != 3 or not all(parts):
skipped += 1
continue
Validate the record shape and required fields before using their positions.
ProjectsFrom Project: Flashcard Quiz
Letting zip hide an unanswered card
With three cards and two responses, the third card is never graded.
See the fix
COMMON MISTAKEfor card, response in zip(cards, responses):
grade(card, response)
FIXif len(cards) != len(responses):
raise ValueError("One response is required for each card")
for card, response in zip(cards, responses):
...
Check the one-to-one contract before pairing the lists.
ProjectsFrom Project: Flashcard Quiz
Normalizing only the learner response
An expected answer stored as DEF still rejects a normalized def response.
See the fix
COMMON MISTAKEif response.strip().casefold() == card["answer"]:
FIXif response.strip().casefold() == card["answer"].strip().casefold():
Apply the same normalization to both strings before comparing them.
ProjectsFrom Project: Flashcard Quiz
Dividing by the number of cards without an empty-deck rule
An empty deck raises ZeroDivisionError.
See the fix
COMMON MISTAKEpercent = correct / len(cards) * 100
FIXpercent = correct / len(cards) * 100 if cards else 0
The core grader returns counts, and any optional percentage display needs an explicit zero-card policy.
Logic & loopsFrom Truthiness, None, and Identity in Python
Treating a valid zero as absent
The genuine score zero is labeled as missing.
See the fix
COMMON MISTAKEif not score:
return 'Not graded'
FIXif score is None:
return 'Not graded'
Test the sentinel specified by the data contract, not every false value.
Logic & loopsFrom Truthiness, None, and Identity in Python
Using or for a None-only default
A supplied zero becomes 100.
See the fix
COMMON MISTAKEscore = supplied or 100
FIXscore = 100 if supplied is None else supplied
The explicit condition preserves all supplied values other than None.
CollectionsFrom Slice Python Sequences Without Losing the Edges
Forgetting that stop is excluded
The third requested index is missing.
See the fix
COMMON MISTAKEitems[1:3] # expect indexes 1, 2, 3
FIXitems[1:4] # indexes 1, 2, 3
Choose a stop one past the final positive index.
CollectionsFrom Slice Python Sequences Without Losing the Edges
Assuming minus zero means an empty tail
For count zero, the entire list is returned.
See the fix
COMMON MISTAKEreturn items[-count:]
FIXif count == 0:
return []
return items[-count:]
Zero needs its own branch because -0 equals 0.
CollectionsFrom Shallow vs Deep Copy in Python
Mistaking an outer copy for full isolation
The input's nested score changes too.
See the fix
COMMON MISTAKEupdated = report.copy()
updated['scores'][0]['points'] = 10
FIXfrom copy import deepcopy
updated = deepcopy(report)
updated['scores'][0]['points'] = 10
A shallow copy retains the original scores list and row dictionaries.
CollectionsFrom Shallow vs Deep Copy in Python
Using equality to prove independence
Equal contents do not show whether nested objects are shared.
See the fix
COMMON MISTAKEassert copied == original # therefore no shared objects
FIXassert copied == original
assert copied is not original
assert copied['scores'][0] is not original['scores'][0]
Inspect identity at the mutable level whose independence matters.
Functions & classesFrom Collect and Unpack Function Arguments
Passing a list as one positional score
scores contains one list; sum(scores) tries to add a list to zero.
See the fix
COMMON MISTAKEbuild_report('Ada', [2, 3])
FIXbuild_report('Ada', *[2, 3])
Use * at the call site when each element should become a separate positional argument.
Functions & classesFrom Collect and Unpack Function Arguments
Treating a keyword-only option as positional
All three numbers enter scores; scale remains its default.
See the fix
COMMON MISTAKEdescribe('Ada', 2, 3, 2) # last 2 is scale
FIXdescribe('Ada', 2, 3, scale=2)
An option after *scores must be named explicitly.
Functions & classesFrom Create a Custom Python Exception for Validation
Raising the generic conversion error only
Nonnumeric text raises ValueError, while zero and negatives pass through.
See the fix
COMMON MISTAKEreturn int(text)
FIXtry:
units = int(text)
except ValueError as exc:
raise QuantityError(f'Invalid units: {text!r}') from exc
if units <= 0:
raise QuantityError(f'Invalid units: {text!r}')
return units
Conversion and the positive-quantity rule both belong to the function's contract.
Functions & classesFrom Create a Custom Python Exception for Validation
Swallowing every exception
A bug inside calculate is silently converted into a valid-looking zero.
See the fix
COMMON MISTAKEtry:
return calculate(text)
except:
return 0
FIXexcept ValueError as exc:
raise QuantityError(f'Invalid units: {text!r}') from exc
Catch the expected failure and keep a diagnosable exception path.
Functions & classesFrom Read Python Tracebacks and Fix the Failing Layer
Catching a bug instead of fixing its line
Passing a dictionary to int is hidden and an invalid port looks successful.
See the fix
COMMON MISTAKEtry:
return int(config)
except TypeError:
return 0
FIXtext = config['port']
number = int(text)
Use the traceback to repair the wrong expression before handling expected input failures.
Functions & classesFrom Read Python Tracebacks and Fix the Failing Layer
Defaulting a missing required key
A missing port becomes a misleading out-of-range value instead of identifying the absent key.
See the fix
COMMON MISTAKEtext = config.get('port', 0)
Required fields should fail clearly when absent; KeyError identifies the missing key.
Logic & loopsFrom Calculate Money with Decimal
Constructing Decimal from an existing float
The object retains the binary float's approximation instead of an exact tenth.
See the fix
COMMON MISTAKEprice = Decimal(0.1)
FIXprice = Decimal('0.1')
Parse the original decimal text before binary floating-point changes it.
Logic & loopsFrom Calculate Money with Decimal
Returning an unrounded amount
Results may have more than two fractional digits, such as 1.005.
See the fix
COMMON MISTAKEreturn str(price * (1 - rate))
FIXreturn str(amount.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP))
State a cent precision and explicit rounding rule at the output boundary.
CollectionsFrom Build Queues and Stacks with deque
Using stack removal for a queue
Linus is served before Ada.
See the fix
COMMON MISTAKEwaiting = ['Ada', 'Grace', 'Linus']
next_name = waiting.pop()
FIXfrom collections import deque
waiting = deque(['Ada', 'Grace', 'Linus'])
next_name = waiting.popleft()
A FIFO queue removes from the opposite end from append.
CollectionsFrom Build Queues and Stacks with deque
Shifting a list for every request
Each front removal shifts the remaining list items.
See the fix
COMMON MISTAKEwhile waiting:
served.append(waiting.pop(0))
FIXwaiting = deque(waiting)
while waiting:
served.append(waiting.popleft())
deque provides efficient operations at both ends.
CollectionsFrom Schedule Jobs with heapq
Unorderable payloads on a priority tie
The second push raises TypeError when dictionaries are compared.
See the fix
COMMON MISTAKEheapq.heappush(heap, (1, {'id': 'A'}))
heapq.heappush(heap, (1, {'id': 'B'}))
FIXheapq.heappush(heap, (1, 0, {'id': 'A'}))
heapq.heappush(heap, (1, 1, {'id': 'B'}))
A unique counter is a comparable tie breaker.
CollectionsFrom Schedule Jobs with heapq
Treating the heap array as a sorted report
Items after the root may appear out of priority order.
See the fix
COMMON MISTAKEprint([task for _, task in heap])
FIXwhile heap:
print(heapq.heappop(heap))
Repeated pops yield ordered priorities.
CollectionsFrom Find Insertion Points with bisect
Using index for an absent item
ValueError is raised because 25 is not present.
See the fix
COMMON MISTAKEposition = scores.index(25)
FIXposition = bisect_left(scores, 25)
bisect_left finds an insertion position whether or not the value exists.
CollectionsFrom Find Insertion Points with bisect
Searching unsorted values
The returned position cannot guarantee a sorted result.
See the fix
COMMON MISTAKEscores = [20, 10, 40]
position = bisect_left(scores, 15)
FIXscores = sorted(scores)
position = bisect_left(scores, 15)
Establish sorted order before binary search; decide separately whether sorting a copy fits your contract.
Functions & classesFrom Memoize Pure Functions with lru_cache
Caching changing external state
Later calls may reuse an old price.
See the fix
COMMON MISTAKE@lru_cache(maxsize=None)
def current_price(item):
return read_live_price(item)
FIXdef current_price(item):
return read_live_price(item)
A changing data source needs an explicit refresh or cache invalidation policy.
Functions & classesFrom Memoize Pure Functions with lru_cache
Passing an unhashable list as a key
TypeError: unhashable type: 'list'.
See the fix
COMMON MISTAKE@lru_cache(maxsize=None)
def total(values):
return sum(values)
print(total([1, 2]))
FIX@lru_cache(maxsize=None)
def total(values):
return sum(values)
print(total((1, 2)))
A tuple of integers is hashable and can be used as a cache key.
Strings & textFrom Understand Unicode Text and UTF-8 Bytes
Decoding UTF-8 bytes as Latin-1
The output becomes café.
See the fix
COMMON MISTAKEdata = 'café'.encode('utf-8')
print(data.decode('latin-1'))
FIXprint(data.decode('utf-8'))
The decoder must match the data's actual encoding.
Strings & textFrom Understand Unicode Text and UTF-8 Bytes
Treating bytes as characters
The comparison is False because the emoji is four UTF-8 bytes but one code point.
See the fix
COMMON MISTAKEprint(len('🐍'.encode('utf-8')) == len('🐍'))
FIXtext = '🐍'
print(len(text), len(text.encode('utf-8')))
Name the measurement: code points and encoded bytes are different quantities.
Files & setupFrom Convert Aware Datetimes with zoneinfo
Hard-coding a winter offset for summer
A July New York time is one hour wrong.
See the fix
COMMON MISTAKElocal = utc_time - timedelta(hours=5)
FIXlocal = utc_time.astimezone(ZoneInfo('America/New_York'))
Named-zone conversion chooses the offset for the date.
Files & setupFrom Convert Aware Datetimes with zoneinfo
Treating a naive timestamp as UTC
Python interprets the naive time using the host's local timezone.
See the fix
COMMON MISTAKEvalue = datetime.fromisoformat('2026-01-15T12:00:00')
print(value.astimezone(ZoneInfo('America/New_York')))
FIXvalue = datetime.fromisoformat('2026-01-15T12:00:00+00:00')
print(value.astimezone(ZoneInfo('America/New_York')))
Provide an explicit offset before converting a moment.
Functions & classesFrom Choose Composition Over Unnecessary Inheritance
Inheriting from a dependency
Changing to another sender requires changing the class hierarchy.
See the fix
COMMON MISTAKEclass NotificationService(EmailSender):
def notify(self, message):
return self.send(message)
FIXclass NotificationService:
def __init__(self, sender): self.sender = sender
def notify(self, message): return self.sender.send(message)
The service uses a sender rather than being one.
Functions & classesFrom Choose Composition Over Unnecessary Inheritance
Dropping the delegated result
notify returns None even if send produces a receipt.
See the fix
COMMON MISTAKEdef notify(self, message):
self.sender.send(message)
FIXdef notify(self, message):
return self.sender.send(message)
Return the dependency's result when it is part of the service contract.
Functions & classesFrom Use Properties to Keep Python Objects Valid
Calling the setter recursively
Assigning the property inside its own setter repeatedly calls that setter.
See the fix
COMMON MISTAKE@celsius.setter
def celsius(self, value):
self.celsius = value
FIX@celsius.setter
def celsius(self, value):
self._celsius = value
Store into a distinct backing attribute after validation.
Functions & classesFrom Use Properties to Keep Python Objects Valid
Bypassing validation in the constructor
An invalid object can be created even if later writes are checked.
See the fix
COMMON MISTAKEdef __init__(self, celsius):
self._celsius = celsius
FIXdef __init__(self, celsius):
self.celsius = celsius
Route initial assignment through the public property.
Functions & classesFrom Use Properties to Keep Python Objects Valid
Mutating before checking
The object holds a bad value after the exception.
See the fix
COMMON MISTAKEself._celsius = value
if value < -273.15: raise ValueError('too cold')
FIXif value < -273.15: raise ValueError('too cold')
self._celsius = value
Complete validation before committing state.
Files & setupFrom Read Environment Configuration Safely
Using bool on textual flags
The nonempty string 'false' evaluates to True.
See the fix
COMMON MISTAKEdebug = bool(env.get('APP_DEBUG', 'false'))
FIXtext = env.get('APP_DEBUG', 'false').strip().lower()
if text in ('true', '1'):
debug = True
elif text in ('false', '0'):
debug = False
else:
raise ValueError('APP_DEBUG must be true or false')
Recognize both true and false spellings, and reject every other supplied value.
Files & setupFrom Read Environment Configuration Safely
Falling back on malformed supplied configuration
A typo silently changes where the program listens.
See the fix
COMMON MISTAKEtry: port = int(env['APP_PORT'])
except ValueError: port = 8000
FIXtry: port = int(env.get('APP_PORT','8000'))
except ValueError as error: raise ValueError('invalid APP_PORT') from error
Defaults are for absence, not malformed input.
Files & setupFrom Read Environment Configuration Safely
Printing a secret during troubleshooting
The token may be copied into terminal logs or support records.
See the fix
COMMON MISTAKEprint('API_TOKEN', env.get('API_TOKEN'))
FIXprint('API_TOKEN configured:', bool(env.get('API_TOKEN')))
Report presence or a safe status instead of a credential value.
Files & setupFrom Read a JSON API with urllib
Making an unchecked network call
The operation can wait indefinitely or return an unexpected shape.
See the fix
COMMON MISTAKEwith urlopen(url) as response: data = json.load(response)
FIXwith urlopen(request, timeout=5) as response:
data = json.load(response)
if not isinstance(data, list):
raise ValueError('expected list')
The timeout bounds waiting; the shape check rejects a non-list response. Validate individual items before using them, as in the next fix.
Files & setupFrom Read a JSON API with urllib
Assuming valid JSON always has a title
A dictionary, numeric item, or missing title can cause an unrelated error or misleading result.
See the fix
COMMON MISTAKEreturn [item['title'] for item in data]
FIXif not isinstance(data, list):
raise ValueError('expected list')
titles = []
for item in data:
if not isinstance(item, dict):
raise ValueError('expected object')
title = item.get('title')
if not isinstance(title, str) or not title.strip():
raise ValueError('expected nonblank title')
titles.append(title.strip())
return titles
Validate the list and each object before extracting a stripped, nonblank title.
Files & setupFrom Read a JSON API with urllib
Embedding a credential in the URL
URLs may be retained in logs and browser histories.
See the fix
COMMON MISTAKEurl = 'https://api.example.test/items?token=' + token
FIXrequest = Request(url, headers={'Authorization': 'Bearer ' + token})
Follow the service's documented authentication method and avoid logging secrets.
Functions & classesFrom Make Objects Work with repr, ==, and len
Returning False for every foreign object
The other operand cannot try its reflected equality behavior.
See the fix
COMMON MISTAKEif not isinstance(other, Book): return False
FIXif not isinstance(other, Book): return NotImplemented
NotImplemented is a dispatch signal, not an exception.
Functions & classesFrom Make Objects Work with repr, ==, and len
Returning a negative length
len(book) raises ValueError rather than yielding a useful length.
See the fix
COMMON MISTAKEdef __len__(self): return -1
FIXdef __len__(self): return self.pages
Validate pages at construction so the method can return a nonnegative integer.
Functions & classesFrom Make Objects Work with repr, ==, and len
Using a human label as value equality
A formatting change can silently change equality semantics.
See the fix
COMMON MISTAKEreturn repr(self) == repr(other)
FIXreturn (self.title, self.pages) == (other.title, other.pages)
Compare the fields that define the value, not its display text.
Functions & classesFrom Test Dependencies with unittest.mock
Fetching twice accidentally
The dependency is called twice and could return different data or incur two requests.
See the fix
COMMON MISTAKEif fetch_user(user_id):
return fetch_user(user_id)['name']
FIXuser = fetch_user(user_id)
return user['name']
Store the single response before inspecting it.
Functions & classesFrom Test Dependencies with unittest.mock
Checking output but ignoring a wrong argument
A function could call the fake with a different ID and still receive its fixed return value.
See the fix
COMMON MISTAKEassert load_user_label(7, fake) == '7: Ada'
FIXassert load_user_label(7, fake) == '7: Ada'
fake.assert_called_once_with(7)
Verify a contractual interaction when a fixed mock response would hide a wrong call.
Functions & classesFrom Test Dependencies with unittest.mock
Accepting missing data as a real name
A missing name becomes the literal text None.
See the fix
COMMON MISTAKEreturn f"{user_id}: {user.get('name')}"
FIXname = user.get('name')
if not isinstance(name, str) or not name.strip(): raise ValueError('name required')
Reject invalid boundary data explicitly.
Files & setupFrom Build a Small CLI with argparse
Reading ambient argv in a browser task
The result depends on the process running the example, not the supplied exercise input.
See the fix
COMMON MISTAKEoptions = parser.parse_args()
FIXoptions = parser.parse_args(argv)
Inject tokens for deterministic practice and testing.
Files & setupFrom Build a Small CLI with argparse
Assuming integer conversion enforces positivity
Zero and negative integers parse successfully.
See the fix
COMMON MISTAKEparser.add_argument('--limit', type=int)
# assume 0 is rejected
FIXif options.limit < 1: raise ValueError('limit must be positive')
Validate business rules after syntax conversion.
Files & setupFrom Build a Small CLI with argparse
Ignoring unknown options
A misspelled flag can disappear without warning.
See the fix
COMMON MISTAKEoptions, extra = parser.parse_known_args(argv)
# silently discard extra
FIXoptions = parser.parse_args(argv)
For this command, unknown arguments should be errors.
Logic & loopsFrom Compare Floating-Point Results with math.isclose
Exact equality for approximate calculations
0.1 + 0.2 compares unequal to 0.3.
See the fix
COMMON MISTAKEreturn left == right
FIXreturn math.isclose(left, right, rel_tol=1e-9)
Use a documented tolerance when the inputs are approximate calculations.
Logic & loopsFrom Compare Floating-Point Results with math.isclose
Relative tolerance alone at zero
The result is False even if this sensor reading is negligible.
See the fix
COMMON MISTAKEmath.isclose(0.005, 0.0, rel_tol=0.01)
FIXmath.isclose(0.005, 0.0, abs_tol=0.01)
A near-zero threshold is an absolute difference in the measured units.
Files & setupFrom Validate CSV Rows with DictReader
Splitting a CSV record manually
A quoted product such as "Pens, blue" becomes multiple fields.
See the fix
COMMON MISTAKEname, qty = line.split(',')
FIXreader = csv.DictReader(io.StringIO(csv_text))
Use the parser to respect quoting and header order.
Files & setupFrom Validate CSV Rows with DictReader
Trusting every parsed row
Missing or shifted cells fail late or are silently ignored.
See the fix
COMMON MISTAKEquantity = int(row['qty'])
FIXif row.get('qty') is None or None in row:
raise ValueError('malformed row')
quantity = int(row['qty'])
Check row shape before type conversion and domain rules.
Files & setupFrom Process JSON Lines One Record at a Time
Parsing the whole stream as one document
Multiple top-level objects produce Extra data.
See the fix
COMMON MISTAKErecords = json.loads(text)
FIXfor line in io.StringIO(text):
record = json.loads(line)
Decode one nonblank line at a time under the chosen JSON Lines convention.
Files & setupFrom Process JSON Lines One Record at a Time
Trusting every decoded value
Lists, numbers, and records with missing or empty event values fail unpredictably.
See the fix
COMMON MISTAKEcounts[record['event']] += 1
FIXif not isinstance(record, dict) or not isinstance(record.get('event'), str) or not record['event']:
raise ValueError('invalid event')
Validate the application schema after decoding syntax.
Logic & loopsFrom See How Outliers Change Mean and Median
Calling the mean the typical observation
The label suggests an ordinary observation around 22.8, but none is nearby.
See the fix
COMMON MISTAKEprint(mean([2,3,4,5,100])) # typical value
FIXprint('mean', mean(data), 'median', median(data))
Show both summaries and interpret their disagreement.
Logic & loopsFrom See How Outliers Change Mean and Median
Iterating a generator twice
A one-shot iterator may be empty on its second use.
See the fix
COMMON MISTAKEaverage = mean(values)
middle = median(values)
FIXitems = list(values)
average = mean(items)
middle = median(items)
Materialize once when multiple passes are required.
Strings & textFrom Normalize Unicode Text for Search Keys
Using lower alone for all case variants
Straße and STRASSE yield different comparison keys.
See the fix
COMMON MISTAKEreturn text.strip().lower()
FIXreturn unicodedata.normalize('NFC', text.strip().casefold())
casefold handles broader Unicode caseless mappings and NFC resolves canonical forms.
Strings & textFrom Normalize Unicode Text for Search Keys
Removing accents without a product rule
Different written names or words can collapse into the same key.
See the fix
COMMON MISTAKEreturn ''.join(c for c in text if not unicodedata.combining(c))
FIXreturn unicodedata.normalize('NFC', text)
Canonical equivalence preserves meaningful accent distinctions.
Functions & classesFrom Use Enums for Clear Python States
A typo silently skips a string branch
The branch never runs for a correctly spelled running state.
See the fix
COMMON MISTAKEif state == 'runnning':
finish()
FIXif state is JobState.RUNNING:
finish()
Normalize external text to an enum, then compare members.
Functions & classesFrom Use Enums for Clear Python States
A default hides an unknown state
A misspelling may accidentally start a new job.
See the fix
COMMON MISTAKEreturn states.get(text, JobState.QUEUED)
FIXreturn JobState(text.strip().lower())
Let unknown values raise ValueError so the caller can correct the input.
Functions & classesFrom Build Stateful Closures with nonlocal
Rebinding without nonlocal
UnboundLocalError appears on the first call.
See the fix
COMMON MISTAKEdef advance():
count += 1
FIXdef advance():
nonlocal count
count += 1
The declaration targets the enclosing function's binding.
Functions & classesFrom Build Stateful Closures with nonlocal
Moving state to a global
All counters affect the same module-level value.
See the fix
COMMON MISTAKEglobal count
count += 1
FIXnonlocal count
count += 1
Keep each counter's state inside its own factory call.
Functions & classesFrom Choose classmethod or staticmethod with a Parser
Hard-coding the base class
SubReading.from_text returns a Reading instead of a SubReading.
See the fix
COMMON MISTAKEreturn Reading(name, value)
FIXreturn cls(name, value)
The classmethod's cls parameter preserves the calling class.
Functions & classesFrom Choose classmethod or staticmethod with a Parser
Treating bool as a count
True passes validation as the integer one.
See the fix
COMMON MISTAKEisinstance(value, int)
FIXtype(value) is int and value >= 0
An exact-type check rejects bool for this domain contract.
Functions & classesFrom Explain Failures with Exception Chaining
Losing the explicit cause
The new error does not explicitly declare its original cause.
See the fix
COMMON MISTAKEexcept ValueError:
raise ConfigError('invalid port')
FIXexcept ValueError as error:
raise ConfigError('invalid port') from error
Explicit chaining sets __cause__ and clarifies the diagnostic link.
Functions & classesFrom Explain Failures with Exception Chaining
Silently defaulting malformed input
A bad supplied setting is quietly ignored.
See the fix
COMMON MISTAKEexcept ValueError:
return 8000
FIXexcept ValueError as error:
raise ConfigError('port must be an integer') from error
Fail near the configuration boundary with a useful message.
Functions & classesFrom Test Cases with unittest Fixtures and subTest
A test that never asserts
The suite reports success even for a broken subtotal.
See the fix
COMMON MISTAKEdef test_cases(self):
pass
FIXwith self.subTest(prices=prices):
self.assertEqual(subtotal(prices), expected)
Assertions make the test sensitive to wrong results.
Functions & classesFrom Test Cases with unittest Fixtures and subTest
Sharing a mutable fixture across methods
One test can leave state that changes another test.
See the fix
COMMON MISTAKEcases = [] # module-level list reused by tests
FIXdef setUp(self):
self.cases = [([], 0), ([2], 2)]
Construct fresh test data before each method.
Files & setupFrom Build Query Strings with urllib.parse
Appending a second question mark
The new text is added inside an existing query and the space is not encoded.
See the fix
COMMON MISTAKEurl = 'https://example.test/x?page=1'
print(url + '?q=red fox')
FIXfrom urllib.parse import urlsplit, urlunsplit, parse_qsl, urlencode
url = 'https://example.test/x?page=1'
p = urlsplit(url)
q = parse_qsl(p.query, keep_blank_values=True) + [('q', 'red fox')]
print(urlunsplit((p.scheme, p.netloc, p.path, urlencode(q), p.fragment)))
Build one query from pairs, then reassemble the components once.
Files & setupFrom Build Query Strings with urllib.parse
Losing repeated keys in a dictionary
Only the final tag value remains in the dictionary.
See the fix
COMMON MISTAKEparams = dict([('tag', 'a'), ('tag', 'b')])
FIXfrom urllib.parse import urlencode
params = [('tag', 'a'), ('tag', 'b')]
print(urlencode(params))
Keep the ordered pair list when multiple values for a key are allowed.
Strings & textFrom Escape Text for HTML Output
Interpolating raw text into markup
The label can be interpreted as markup instead of displayed literally.
See the fix
COMMON MISTAKElabel = '<b>Hi</b>'
print(f'<span>{label}</span>')
FIXfrom html import escape
label = '<b>Hi</b>'
print(f'<span>{escape(label, quote=True)}</span>')
Escape the variable text at the HTML output boundary.
Strings & textFrom Escape Text for HTML Output
Undoing the escape before output
Character references become angle brackets again before insertion.
See the fix
COMMON MISTAKEfrom html import unescape
print(unescape('<b>Hi</b>'))
FIXfrom html import escape
label = '<b>Hi</b>'
print(escape(label, quote=True))
Keep the text escaped for HTML output; unescape is for decoding input when that is actually required.
Files & setupFrom Check Content with hashlib
Passing a string directly to SHA-256
TypeError: Strings must be encoded before hashing.
See the fix
COMMON MISTAKEimport hashlib
print(hashlib.sha256('abc').hexdigest())
FIXimport hashlib
print(hashlib.sha256('abc'.encode('utf-8')).hexdigest())
Make the text-to-bytes conversion explicit and stable.
Files & setupFrom Check Content with hashlib
Using hash() as a persistent checksum
The built-in hash value can differ across Python processes and is not a cryptographic content digest.
See the fix
COMMON MISTAKEprint(hash('abc'))
FIXimport hashlib
print(hashlib.sha256(b'abc').hexdigest())
Use a defined digest algorithm for reproducible content checks.
Files & setupFrom Read INI Settings with configparser
Treating retrieved text as an integer
A string and integer cannot be added; the operation raises TypeError.
See the fix
COMMON MISTAKEfrom configparser import ConfigParser
p=ConfigParser(); p.read_string('[job]\nretries=3\n')
print(p.get('job','retries') + 1)
FIXfrom configparser import ConfigParser
p=ConfigParser(); p.read_string('[job]\nretries=3\n')
print(p.getint('job','retries') + 1)
Use getint and validate the resulting number.
Files & setupFrom Read INI Settings with configparser
Assuming every percent is interpolation syntax
A lone percent sign raises InterpolationSyntaxError with default interpolation.
See the fix
COMMON MISTAKEfrom configparser import ConfigParser
p=ConfigParser(); p.read_string('[job]\nstatus=100% ready\n'); print(p.get('job','status'))
FIXfrom configparser import ConfigParser
p=ConfigParser(interpolation=None); p.read_string('[job]\nstatus=100% ready\n'); print(p.get('job','status'))
Turn off interpolation when configuration values should be literal.
Files & setupFrom Create ZIP Archives in Memory
Using text methods on bytes without decoding
TypeError because bytes.split expects a bytes separator, not str.
See the fix
COMMON MISTAKEfrom io import BytesIO
from zipfile import ZipFile
b=BytesIO()
with ZipFile(b,'w') as z: z.writestr('x.txt', b'one two')
with ZipFile(BytesIO(b.getvalue())) as z: print(z.read('x.txt').split(' '))
FIXfrom io import BytesIO
from zipfile import ZipFile
b=BytesIO()
with ZipFile(b,'w') as z: z.writestr('x.txt', b'one two')
with ZipFile(BytesIO(b.getvalue())) as z: print(z.read('x.txt').decode('utf-8').split(' '))
Decode when converting stored bytes back to text.
Files & setupFrom Create ZIP Archives in Memory
Allowing path-like names in a simple archive
The entry name describes a relative parent path that is inappropriate for this basename-only exercise.
See the fix
COMMON MISTAKEarchive.writestr('../notes.txt', b'hello')
FIXname='notes.txt'
assert '/' not in name and '\\' not in name
archive.writestr(name, b'hello')
Validate names before archive creation; extraction also needs a separate policy.
ProjectsFrom Project: Inventory Reorder Planner
Ordering at the threshold
An item with stock exactly equal to minimum appears unexpectedly.
See the fix
COMMON MISTAKEif stock <= minimum:
Follow the stated strict boundary.
ProjectsFrom Project: Inventory Reorder Planner
Ordering only up to the minimum
The order leaves inventory at the trigger threshold instead of the desired target.
See the fix
COMMON MISTAKEquantity = minimum - stock
FIXquantity = target - stock
The target determines the replenishment amount.
ProjectsFrom Project: Booking Conflict Detector
Using inclusive endpoints
Back-to-back bookings are reported as a conflict.
See the fix
COMMON MISTAKEa_start <= b_end and b_start <= a_end
FIXa_start < b_end and b_start < a_end
Strict comparisons match half-open occupancy intervals.
ProjectsFrom Project: Booking Conflict Detector
Checking only whether a start is inside another interval
The case where B fully contains A can be missed.
See the fix
COMMON MISTAKEa_start <= b_start < a_end
FIXa_start < b_end and b_start < a_end
The symmetric condition covers containment and partial overlap.
ProjectsFrom Project: CSV Import Validator with Row Errors
Returning only valid rows
A user cannot tell which source rows were rejected or why.
See the fix
COMMON MISTAKEif invalid: continue
FIXerrors.append((reader.line_num, reason))
continue
Every rejected row needs a traceable report entry.
ProjectsFrom Project: CSV Import Validator with Row Errors
Failing the whole import at the first bad cell
Later valid rows are never inspected.
See the fix
COMMON MISTAKEqty = int(row['qty']) # uncaught
FIXtry: qty = int(raw_qty)
except ValueError:
errors.append((reader.line_num, 'invalid qty'))
continue
Catch ordinary row-level validation errors under the chosen partial-success contract.
ProjectsFrom Project: Verify a ZIP Archive Against a Manifest
Checking names but not content
A changed b.txt is reported as healthy.
See the fix
COMMON MISTAKEchanged = [] # names match, so files match
FIXchanged = [name for name in common if hashlib.sha256(archive.read(name)).hexdigest() != manifest[name]]
Hash member bytes for every expected present file.
ProjectsFrom Project: Verify a ZIP Archive Against a Manifest
Extracting an untrusted archive to inspect it
Inspection writes every member to disk and may expose path or resource risks.
See the fix
COMMON MISTAKEarchive.extractall(output_dir)
FIXdata = archive.read(name)
digest = hashlib.sha256(data).hexdigest()
This small fixture verifier only needs bytes, not extraction.
ProjectsFrom Project: Offline Web Crawler on a Fixture Graph
Never recording scheduled URLs
A home/about cycle can revisit pages until the limit is exhausted.
See the fix
COMMON MISTAKEqueue.extend(all_links)
FIXif target in pages and target not in queued:
queued.add(target)
queue.append(target)
Mark a fixture URL when it is scheduled, not after another path finds it.
ProjectsFrom Project: Offline Web Crawler on a Fixture Graph
Claiming the browser fixture is a live crawl
Arbitrary network requests are outside this deterministic browser-console exercise.
See the fix
COMMON MISTAKEhtml = urllib.request.urlopen(url).read()
Keep the algorithm offline; teach real network crawling separately with operational safeguards.
Files & setupFrom Query SQLite Safely with Parameters
Interpolating an untrusted value
Quotes break the query and SQL-looking values can alter its meaning.
See the fix
COMMON MISTAKEquery = f"SELECT name FROM items WHERE category = '{category}'"
FIXconnection.execute('SELECT name FROM items WHERE category = ?', (category,))
Bind values; do not manufacture SQL syntax from external text.
Files & setupFrom Query SQLite Safely with Parameters
Assuming rows arrive in insertion order
A later query plan can return a different order.
See the fix
COMMON MISTAKESELECT name FROM items WHERE category = ?
FIXSELECT name FROM items WHERE category = ? ORDER BY name
Specify the order required by the interface.
Functions & classesFrom Generate Secure Random Tokens
Seeding a token generator
Anyone with the seed can reproduce the token.
See the fix
COMMON MISTAKErandom.seed(7)
token = str(random.random())
FIXtoken = secrets.token_urlsafe(16)
Use security-oriented randomness and enough random bytes.
Functions & classesFrom Generate Secure Random Tokens
Logging the credential
A copied log now contains a usable secret.
See the fix
COMMON MISTAKEprint('issued token', token)
FIXprint('token issued; characters:', len(token))
Show harmless metadata when debugging, not the credential.
Logic & loopsFrom Make Random Simulations Reproducible
Changing the shared generator
Other code's results depend on this function's call order.
See the fix
COMMON MISTAKErandom.seed(seed)
value = random.random()
FIXrng = random.Random(seed)
value = rng.random()
Keep simulation state local.
Logic & loopsFrom Make Random Simulations Reproducible
Recreating the generator for every trial
Every trial repeats the first value.
See the fix
COMMON MISTAKEfor _ in range(count):
value = random.Random(seed).random()
FIXrng = random.Random(seed)
for _ in range(count):
value = rng.random()
Create once, then advance that instance.
CollectionsFrom Layer Settings with ChainMap
Reversing the priority order
A default hides an explicit override.
See the fix
COMMON MISTAKEChainMap(defaults, environment, cli)
FIXChainMap(cli, environment, defaults)
Place the highest-priority source first.
CollectionsFrom Layer Settings with ChainMap
Expecting a deep merge
The top db mapping lacks port and raises KeyError.
See the fix
COMMON MISTAKEChainMap({'db': {'host':'new'}}, {'db': {'port':5432}})['db']['port']
FIXDefine a nested merge policy, or use flat keys
ChainMap combines top-level lookups, not nested values.
Files & setupFrom Replace a File Safely with a Temporary File
Writing directly to the final path
The old content is lost if writing fails partway.
See the fix
COMMON MISTAKEPath(target).write_text(text, encoding='utf-8')
FIXwrite a temporary file, close it, then os.replace(temp_name, target)
Stage complete content before changing the target name.
Files & setupFrom Replace a File Safely with a Temporary File
Leaving a failed temporary file behind
An exception can leave an orphaned name and an open file.
See the fix
COMMON MISTAKEtemp = NamedTemporaryFile(delete=False); temp.write(data); os.replace(temp.name, target)
FIXclose the temporary file; use finally to unlink its name if it still exists
Failure cleanup is part of the replacement pattern.
Files & setupFrom Parse and Build Small XML Documents
Building XML by concatenation
A title containing & produces invalid XML.
See the fix
COMMON MISTAKExml = '<title>' + title + '</title>'
FIXET.SubElement(book, 'title').text = title
Assign text and let ElementTree escape it.
Files & setupFrom Parse and Build Small XML Documents
Assuming optional fields exist
A missing year fails with a confusing conversion error.
See the fix
COMMON MISTAKEyear = int(book.get('year'))
FIXif book.get('year') is None: raise ValueError('missing year')
Validate the catalog schema before conversion.
Strings & textFrom Compare Text with Unified Diffs
Dropping all line endings
Concatenated diff output can put markers on the same line.
See the fix
COMMON MISTAKEbefore.splitlines()
FIXbefore.splitlines(keepends=True)
Preserve newline characters on input lines.
Strings & textFrom Compare Text with Unified Diffs
Treating every + as added content
The +++ after file header is counted as an added line.
See the fix
COMMON MISTAKEadded = [line for line in diff if line.startswith('+')]
FIXskip the first two file headers before counting content lines
Unified diff has metadata as well as content markers.
Logic & loopsFrom Calculate Exact Ratios with Fraction
Converting an approximated float
The denominator reflects the binary float, not exactly one tenth.
See the fix
COMMON MISTAKEFraction(0.1)
Pass decimal text when the decimal value is the source of truth.
Logic & loopsFrom Calculate Exact Ratios with Fraction
Dividing by invalid weights
Zero total weight divides by zero; negative weights make confusing shares.
See the fix
COMMON MISTAKEleft = total * left_weight / (left_weight + right_weight)
FIXif type(left_weight) is not int or left_weight <= 0: raise ValueError('positive weight required')
Validate both ratio weights before calculation.
Files & setupFrom Validate IP Addresses and Networks with ipaddress
Matching text prefixes
192.168.10.5 is incorrectly accepted.
See the fix
COMMON MISTAKEaddress.startswith('192.168.1')
FIXipaddress.ip_address(address) in ipaddress.ip_network('192.168.1.0/24')
Use CIDR arithmetic for membership.
Files & setupFrom Validate IP Addresses and Networks with ipaddress
Silently normalizing a mistyped network
A host address can turn into a larger access range without notice.
See the fix
COMMON MISTAKEipaddress.ip_network(text, strict=False)
FIXipaddress.ip_network(text)
Strict parsing makes the configuration error visible.
Files & setupFrom Copy and Move Files Safely with shutil
Using move for a requested copy
The source path disappears after the operation.
See the fix
COMMON MISTAKEshutil.move(source, destination)
FIXshutil.copy2(source, destination)
Match the operation to the intended source outcome.
Files & setupFrom Copy and Move Files Safely with shutil
Ignoring a destination collision
An existing target can be overwritten.
See the fix
COMMON MISTAKEshutil.copy2(source, destination)
FIXif Path(destination).exists():
raise FileExistsError(destination)
shutil.copy2(source, destination)
State and enforce a collision policy before the transfer.
Functions & classesFrom Configure Callbacks with functools.partial
Calling during registration
callback is a string and cannot be invoked later.
See the fix
COMMON MISTAKEcallback = format_event('ERROR', 'ready')
FIXcallback = partial(format_event, 'ERROR')
Pass a configured callable, then supply the message later.
Functions & classesFrom Configure Callbacks with functools.partial
Binding a mutable shared default
Later calls unexpectedly share and mutate one list.
See the fix
COMMON MISTAKEcallback = partial(add_item, items=[])
FIXcallback = partial(add_item, items=tuple())
Prefer immutable bound configuration or create fresh state per call.
Functions & classesFrom Define Structural Interfaces with typing.Protocol
Assuming a Protocol requires inheritance
Unrelated useful implementations are needlessly coupled.
See the fix
COMMON MISTAKEclass Html(Renderable): ... # required for all implementations
FIXclass Html:
def render(self) -> str: ...
Structural typing accepts matching members without inheritance.
Functions & classesFrom Define Structural Interfaces with typing.Protocol
Using a presence check as complete validation
A noncallable render attribute may still pass the presence check.
See the fix
COMMON MISTAKEif isinstance(item, Renderable): return item.render()
FIXmethod = getattr(item, 'render', None)
if not callable(method): raise TypeError('render required')
Validate the runtime behavior this function relies on.
Functions & classesFrom Inspect Function Signatures and Validate Arguments
Executing while trying to validate
A notification is sent before the proposed call is approved.
See the fix
COMMON MISTAKEcallback(**kwargs) # validation only
FIXinspect.signature(callback).bind(**kwargs)
Bind the argument shape without executing side effects.
Functions & classesFrom Inspect Function Signatures and Validate Arguments
Treating annotations as runtime guards
A wrongly typed or invalid address can still pass.
See the fix
COMMON MISTAKEsignature.bind(**kwargs) # all values are now safe
FIXbound = signature.bind(**kwargs)
# Validate domain values separately.
Signature binding does not check value semantics or type hints.
Functions & classesFrom Parse Python Literals Without eval
Using eval as a parser
The input can execute code instead of supplying data.
See the fix
COMMON MISTAKEvalues = eval(user_text)
FIXvalues = ast.literal_eval(user_text)
Parse literal syntax with a limited parser, then validate it.
Functions & classesFrom Parse Python Literals Without eval
Accepting every parsed literal
Tuples, booleans, huge values, or nested data slip through.
See the fix
COMMON MISTAKEreturn ast.literal_eval(text)
FIXif type(values) is not list or any(type(x) is not int for x in values):
raise ValueError('integer list required')
Apply the application's exact shape and range contract.
Logic & loopsFrom Schedule Dependent Tasks with Topological Sort
Alphabetical sorting of task names
deploy can appear before test.
See the fix
COMMON MISTAKEreturn sorted(dependencies)
FIXready = [name for name, needs in dependencies.items() if not needs]
# Release dependents only after their prerequisites.
Use graph relationships, not labels, to establish order.
Logic & loopsFrom Schedule Dependent Tasks with Topological Sort
Returning a partial order on a cycle
The plan omits work without explaining why.
See the fix
COMMON MISTAKEreturn order # even if some tasks remain
FIXif len(order) != len(dependencies):
raise ValueError('dependency cycle')
A cycle means no complete valid ordering exists.
Strings & textFrom Build and Parse Email Messages Offline
Claiming a constructed message was sent
No transport or mailbox interaction occurred.
See the fix
COMMON MISTAKEmessage = EmailMessage(); print('delivered')
FIXmessage = EmailMessage(); print('message built locally')
Keep message construction separate from delivery.
Strings & textFrom Build and Parse Email Messages Offline
Putting unvalidated multiline input in a header
A supplied newline violates this function's header contract.
See the fix
COMMON MISTAKEmessage['Subject'] = user_subject
FIXif '\n' in user_subject or '\r' in user_subject:
raise ValueError('single-line subject required')
Validate header shape before assignment.
Strings & textFrom Encode Binary Data with Python base64
Passing text directly
TypeError: a bytes-like object is required
See the fix
COMMON MISTAKEbase64.b64encode('Python')
FIXbase64.b64encode('Python'.encode('utf-8'))
The source text must be converted to bytes before a binary-to-text representation is applied.
Strings & textFrom Encode Binary Data with Python base64
Using str() on encoded bytes
The result contains a b prefix and quotation marks.
See the fix
COMMON MISTAKEstr(base64.b64encode(b'Python'))
FIXbase64.b64encode(b'Python').decode('ascii')
Decode the ASCII encoded bytes into an actual text token instead of formatting the bytes object for debugging.
Files & setupFrom Authenticate Messages with Python hmac
Hashing without the key
A different sender can recalculate the same kind of tag.
See the fix
COMMON MISTAKEhashlib.sha256(message).hexdigest()
FIXhmac.new(key, message, hashlib.sha256).hexdigest()
The key must participate in the tag computation; printing more hash characters does not add authentication.
Files & setupFrom Authenticate Messages with Python hmac
Comparing tags with ==
The comparison may leak timing information about matching prefixes.
See the fix
COMMON MISTAKEprovided_tag == expected_tag
FIXhmac.compare_digest(provided_tag, expected_tag)
Use the dedicated comparison for secret tags, then separately design replay protection and key handling.
Functions & classesFrom Create Stable and Random UUIDs in Python
Parsing a plain label
ValueError: badly formed hexadecimal UUID string
See the fix
COMMON MISTAKEuuid.UUID('guest')
FIXuuid.uuid5(uuid.NAMESPACE_URL, 'guest')
The UUID constructor parses an existing identifier; uuid5 derives one from a namespace and name.
Functions & classesFrom Create Stable and Random UUIDs in Python
Using uuid4 for a stable lookup
Repeated calls do not reproduce the same mapping.
See the fix
COMMON MISTAKEreturn str(uuid.uuid4())
FIXreturn str(uuid.uuid5(uuid.NAMESPACE_URL, normalized_name))
Choose uuid5 for a stable namespace/name mapping and normalize the name consistently.
Files & setupFrom Compress Text and Bytes with Python gzip
The visible failure
Passing str directly to gzip.compress raises TypeError because the compressor expects bytes.
See the fix
COMMON MISTAKEgzip.compress('hello')
FIXgzip.compress('hello'.encode('utf-8'), mtime=0)
Use gzip.compress(text.encode('utf-8'), mtime=0) to make a reproducible sample payload, and gzip.decompress(payload).decode('utf-8') to recover the text. The mtime setting affects gzip header metadata; it is not a security feature and does not promise equal compression across every Python or zlib version.
Files & setupFrom Compress Text and Bytes with Python gzip
Missing the edge condition
A byte string that is not a gzip member raises an error during decompression, so blindly decoding arbitrary input is unsafe.
See the fix
COMMON MISTAKEgzip.decompress(b'not gzip').decode('utf-8')
FIXvalidate and limit the compressed input before consuming it
Compression can drastically expand during decompression for specially prepared input. The tiny classroom example does not enforce a production memory budget. Handle invalid data and size limits where the bytes enter a real service, and never treat gzip as encryption.
Files & setupFrom Pack Binary Records with Python struct
The visible failure
Unpacking a one-byte buffer with a three-byte format raises struct.error.
See the fix
COMMON MISTAKEstruct.unpack('>HB', b'\x03')
FIXif len(packet) == struct.calcsize('>HB'): struct.unpack('>HB', packet)
Use struct.pack('>HB', identifier, status) to build the packet and struct.unpack('>HB', packet) to recover a tuple. Range-check integers against the format before packing if user input is involved. An unsigned short supports 0 through 65535 and an unsigned byte supports 0 through 255.
Files & setupFrom Pack Binary Records with Python struct
Missing the edge condition
The verifier attempts to decode a truncated record and deliberately catches struct.error.
See the fix
COMMON MISTAKEstruct.pack('HB', identifier, status)
FIXstruct.pack('>HB', identifier, status)
A binary protocol must define length, byte order, version and validation explicitly. Native struct formats may add platform-dependent padding, so avoid an implicit native format for data exchanged between systems. The example is a local three-byte protocol, not a network framing implementation.
Files & setupFrom Test Text Streams with Python StringIO
The visible failure
Reading a StringIO stream advances its cursor.
See the fix
COMMON MISTAKEstream.read(); stream.read()
FIXstream.read(); stream.seek(0); stream.read()
Implement count_lines(stream) as a single pass over the provided text stream. StringIO makes it easy to test that behavior without filesystem setup. To repeat the call on the same object, explicitly rewind with seek(0), then compare the count again.
Files & setupFrom Test Text Streams with Python StringIO
Missing the edge condition
An empty in-memory stream has zero lines.
See the fix
COMMON MISTAKElen(stream.read().split('\n'))
FIXsum(1 for _ in stream)
StringIO is a text stream, not a byte stream; use BytesIO when code expects bytes. It is also a test aid, not evidence that a function handles disk permissions or very large files. Those are separate integration concerns.
Files & setupFrom Read TOML Configuration with Python tomllib
The visible failure
Indexing a missing nested key raises KeyError, while blindly using get with a default can hide a typo in a required setting.
See the fix
COMMON MISTAKEconfig['service']['retry']
FIXvalidate config['service']['retries'] after parsing
Use tomllib.loads for a text string and tomllib.load for a binary file object. After parsing, validate required tables and values explicitly; raise ValueError with a useful message when the contract is not met. tomllib does not write TOML, so do not assume a dumps method exists.
Files & setupFrom Read TOML Configuration with Python tomllib
Missing the edge condition
An invalid TOML document raises TOMLDecodeError before any application validation runs.
See the fix
COMMON MISTAKEreturn config.get('service', {}).get('retries', 3)
FIXraise ValueError when required retries is missing or invalid
Configuration may contain secrets; avoid printing complete parsed dictionaries in production logs. The classroom string contains public sample settings only. Keep the distinction between syntax errors, missing settings and invalid values clear to a caller who must fix a configuration file.
Files & setupFrom Find Files with Python glob and fnmatch
The visible failure
fnmatch may normalize case according to the host operating system.
See the fix
COMMON MISTAKEfnmatch.fnmatch(name, '*.py')
FIXfnmatch.fnmatchcase(name, '*.py')
Use fnmatch.fnmatchcase(name, '*.py') for a case-sensitive in-memory filter, and sorted(...) for deterministic results. When searching actual directories, use pathlib.Path.glob or glob.glob but validate the search root and do not assume a pattern by itself enforces access controls.
Files & setupFrom Find Files with Python glob and fnmatch
Missing the edge condition
The final run includes a nested name like src/main.py.
See the fix
COMMON MISTAKEreturn [name for name in names if name.endswith('.py')]
FIXreturn sorted(name for name in names if '/' not in name and fnmatch.fnmatchcase(name, '*.py'))
Shell-style glob patterns are not regular expressions. Asterisks and question marks have different syntax from re, and a pattern is not a security boundary. Define whether matching is case-sensitive and whether paths or base names are allowed before using results to delete or publish files.
Logic & loopsFrom Benchmark Tiny Python Functions with timeit
The visible failure
Calling time.perf_counter once before and once after a single tiny operation gives a number, but its precision does not make it trustworthy.
See the fix
COMMON MISTAKEtime.perf_counter() around one tiny call
FIXtimeit.repeat(callable, number=many_calls, repeat=several_runs)
Write per_call(total_seconds, number) to divide a measured duration by a positive integer call count. The function is simple by design: it keeps the unit conversion honest, while the demonstration shows how timeit supplies each total. A benchmark needs a stated interpreter, machine and input size before drawing conclusions.
Logic & loopsFrom Benchmark Tiny Python Functions with timeit
Missing the edge condition
Zero calls cannot yield a meaningful per-call number and would divide by zero.
See the fix
COMMON MISTAKEreturn total_seconds
FIXreturn total_seconds / number after validating number > 0
Benchmark results are observations, not language guarantees. The local demo does not prove which implementation is faster on the user's machine or production workload. Start by confirming both functions return the same value; then measure realistic inputs and report enough context to interpret the numbers.
Logic & loopsFrom Find a Shortest Unweighted Path with BFS
The visible failure
Using list.pop() treats the frontier as a LIFO stack, producing depth-first traversal.
See the fix
COMMON MISTAKEfrontier.pop()
FIXfrontier.popleft() using collections.deque
Initialize deque with the path containing start. Pop paths from the left, append neighbor paths on the right, and mark a node visited when enqueuing it. Return the first path ending at goal. For this small teaching graph, storing whole paths is readable; larger graphs can store predecessor pointers instead.
Logic & loopsFrom Find a Shortest Unweighted Path with BFS
Missing the edge condition
If start equals goal, the shortest path is the one-node list.
See the fix
COMMON MISTAKEreturn [] when no route exists
FIXreturn None for unreachable goal
BFS minimizes the number of edges only when edges have equal cost. It does not know that one route is geographically faster or cheaper than another. For large graphs, a queue of complete path copies consumes extra memory; reconstruct a path from parent pointers when scalability matters.
Logic & loopsFrom Detect Directed Graph Cycles with DFS
The visible failure
A single visited set is insufficient for directed cycle detection.
See the fix
COMMON MISTAKEif neighbor in visited: return True
FIXif neighbor in active: return True; if neighbor in done: continue
Maintain active and done sets. Entering a node adds it to active; after all descendants return without a cycle, move it to done. On a neighbor already active return True; on a done neighbor continue. Start DFS from every graph key to cover disconnected components; recursive visits also reach referenced neighbors.
Logic & loopsFrom Detect Directed Graph Cycles with DFS
Missing the edge condition
A self-loop such as X to X is a cycle; an empty graph is not.
See the fix
COMMON MISTAKEsearch only graph.keys() without referenced nodes
FIXinclude nodes appearing in adjacency values too
This detector is for directed edges. For an undirected graph, the immediate edge back to a parent needs special treatment. Document graph representation and depth limits before using this recursive version on untrusted large graphs.
Logic & loopsFrom Solve Coin Change with Dynamic Programming
The visible failure
Greedy is correct for some coin systems but not for arbitrary denominations.
See the fix
COMMON MISTAKEtake the largest affordable coin repeatedly
FIXcompute dp[amount] from every reachable dp[amount - coin]
Initialize dp[0] to zero and all other entries to an unreachable marker. For each amount, consider only positive coins not larger than that amount; if dp[amount - coin] is reachable, update dp[amount]. Return None when the target stays unreachable. Reject negative targets and nonpositive denominations before building the table.
Logic & loopsFrom Solve Coin Change with Dynamic Programming
Missing the edge condition
Target zero requires zero coins.
See the fix
COMMON MISTAKEreturn infinity for unreachable amount
FIXreturn None for unreachable amount
This algorithm minimizes coin count, not monetary value or weight; denominations define the available moves. Production financial calculations have other constraints and should not infer optimality from a greedy shortcut without proving it for the specific coin system.
Logic & loopsFrom Compute a Moving Average with a Sliding Window
The visible failure
A range ending at len(values) - size omits the final valid starting position.
See the fix
COMMON MISTAKErange(len(values) - size)
FIXinitialize first window, then update through index len(values)-1
Initialize a sum for the first size values, append its average, then for each later index add values[index] and subtract values[index - size]. Append one average after each update. Reject nonpositive window sizes, and return an empty list when size exceeds the data length.
Logic & loopsFrom Compute a Moving Average with a Sliding Window
Missing the edge condition
A window size equal to the data length yields one average.
See the fix
COMMON MISTAKEaverage a partial final window
FIXreturn [] if size > len(values)
Floating-point averages can have tiny representation differences; tests should use appropriate tolerance for nonexact decimals. The lesson uses small integer inputs whose output is exactly representable. For streaming data too large to store, the same running-sum idea can be paired with a bounded deque.
Logic & loopsFrom Count Connected Components with Union-Find
The visible failure
Starting with one component per node and subtracting the number of edges gives the wrong answer for a cycle.
See the fix
COMMON MISTAKElen(nodes) - len(edges)
FIXdecrement only when union joins two different roots
Initialize parent[node] = node for every declared node, including isolated ones. A find function follows parent links and compresses paths. Union roots for each undirected edge, decrementing the component count only when the roots differ. In this compact demo an unknown edge endpoint raises KeyError from the parent lookup; validate endpoints explicitly if the caller needs a friendlier error.
Logic & loopsFrom Count Connected Components with Union-Find
Missing the edge condition
An empty graph has zero components, and four isolated nodes have four.
See the fix
COMMON MISTAKEinitialize parents from edge endpoints only
FIXinitialize parents for every declared node
This small implementation uses path compression and is adequate for the exercise. For very large dynamic graphs, union by rank or size can further control tree height. Union-find answers connectivity under added edges; removing edges requires another strategy.
Logic & loopsFrom Answer Range Sum Queries with Prefix Sums
The visible failure
If the prefix array starts at the first input value instead of a leading zero, empty ranges and ranges beginning at zero become awkward and off-by-one errors appear.
See the fix
COMMON MISTAKEprefix = [values[0]]
FIXprefix = [0]; append(prefix[-1] + value) for each value
Build prefix by starting with [0] and appending the last total plus each next value. A query validates 0 <= start <= end <= len(values), then subtracts two stored totals. This precomputation costs one pass; each later query uses constant-time indexing and subtraction.
Logic & loopsFrom Answer Range Sum Queries with Prefix Sums
Missing the edge condition
An empty range where start equals end returns zero, and the full range uses the final prefix entry.
See the fix
COMMON MISTAKEprefix[end+1] - prefix[start]
FIXprefix[end] - prefix[start] for [start,end)
Prefix sums are appropriate when source values stay unchanged between queries. After a value changes, later prefix totals become stale and must be rebuilt or maintained with another data structure. Keep the half-open interval convention visible in function names and tests.
Strings & textFrom Capture and Replace Text with Python Regex
Calling group on a missing match
AttributeError occurs when search returns None.
See the fix
COMMON MISTAKEre.search(r'ORD-[0-9]+', text).group()
FIXmatch = re.search(r'ORD-[0-9]+', text)
if match is not None:
print(match.group())
Check the optional match object before reading a group.
Strings & textFrom Capture and Replace Text with Python Regex
Matching only a valid prefix
The first four digits are changed and the fifth remains.
See the fix
COMMON MISTAKEre.sub(r'ORD-[0-9]{4}', 'order', 'ORD-12345')
FIXre.sub(r'\bORD-[0-9]{4}\b', 'order', 'ORD-12345')
Both boundaries make the pattern match a complete code.
Functions & classesFrom Validate Dataclasses with __post_init__
Assuming annotations enforce values
Range(8, 2) constructs even though the bounds are reversed.
See the fix
COMMON MISTAKE@dataclass
class Range:
low: int
high: int
FIXdef __post_init__(self):
if self.low > self.high:
raise ValueError('low must not exceed high')
Annotations document intended types; explicit logic enforces the relationship.
Functions & classesFrom Validate Dataclasses with __post_init__
Making derived state a caller argument
The caller may pass a span inconsistent with the bounds.
See the fix
COMMON MISTAKEspan: int = 0
FIXspan: int = field(init=False)
# Set it in __post_init__.
Derive the value from low and high in one place.
Functions & classesFrom Manage Async Tasks with TaskGroup and Cancellation
Reading results before the group finishes
InvalidStateError may occur because the child has not completed.
See the fix
COMMON MISTAKEtask = group.create_task(job())
print(task.result())
FIXasync with asyncio.TaskGroup() as group:
task = group.create_task(job())
print(task.result())
Wait for context exit before reading the result.
Functions & classesFrom Manage Async Tasks with TaskGroup and Cancellation
Suppressing cancellation in a child
The group may observe misleading child state and cleanup semantics.
See the fix
COMMON MISTAKEtry:
await work()
except asyncio.CancelledError:
return None
FIXtry:
await work()
finally:
release_resource()
Clean up in finally; generally let cancellation propagate.
CollectionsFrom Merge Dictionaries and Handle Duplicate Keys
Assuming union rejects collisions
A duplicate right-hand value silently replaces the left value.
See the fix
COMMON MISTAKEcombined = left | right
FIXif left.keys() & right.keys():
raise ValueError('duplicate key')
combined = left | right
Check the policy before applying union.
CollectionsFrom Merge Dictionaries and Handle Duplicate Keys
Assigning update's return value
combined is None and left has changed.
See the fix
COMMON MISTAKEcombined = left.update(right)
FIXcombined = left | right
Use union for a fresh result, or call update when in-place mutation is intended.
CollectionsFrom Compare Groups with Python Set Operations
Counting repeated members twice
A repeated left-side name appears twice in common.
See the fix
COMMON MISTAKE[name for name in left if name in right]
FIXsorted(set(left) & set(right))
The comparison is about unique membership, not event counts.
CollectionsFrom Compare Groups with Python Set Operations
Reversing set difference
The function returns people only on the right.
See the fix
COMMON MISTAKEright_set - left_set # left-only
FIXleft_set - right_set # left-only
Difference keeps values from the first operand that are absent from the second.
Files & setupFrom Read Nested JSON with Explicit Missing-Field Rules
Indexing every field directly
Missing keys or null values raise unrelated KeyError or TypeError exceptions.
See the fix
COMMON MISTAKErecord['user']['address']['city']
FIXaddress = user.get('address')
if address is None:
return None
Handle optional absence before deeper traversal.
Files & setupFrom Read Nested JSON with Explicit Missing-Field Rules
Chaining get without checking types
A present list or null can still raise AttributeError or mask a malformed shape.
See the fix
COMMON MISTAKErecord.get('user', {}).get('address', {}).get('city')
FIXif not isinstance(address, dict):
raise ValueError('address must be an object')
Verify each present container's type explicitly.
Files & setupFrom Read and Write CSV with Quoting and Dialects
Splitting a CSV row on commas
A quoted address containing a comma becomes two fields.
See the fix
COMMON MISTAKEline.split(',')
FIXcsv.reader(io.StringIO(line))
Use the parser that understands quoted separators.
Files & setupFrom Read and Write CSV with Quoting and Dialects
Joining fields to write CSV
Commas or quotes inside values corrupt the output.
See the fix
COMMON MISTAKE','.join(row)
FIXcsv.writer(stream).writerow(row)
The writer applies the dialect's escaping rules.
Files & setupFrom Serialize Datetime and Decimal Values to JSON
Passing datetime directly to json.dumps
TypeError reports an unsupported datetime object.
See the fix
COMMON MISTAKEjson.dumps({'at': datetime.now()})
FIXjson.dumps(data, default=encode_special)
Define a representation for values JSON lacks.
Files & setupFrom Serialize Datetime and Decimal Values to JSON
Using default=str for everything
An accidental set becomes a string instead of failing validation.
See the fix
COMMON MISTAKEjson.dumps(data, default=str)
FIXif isinstance(value, Decimal): return str(value)
raise TypeError('unsupported type')
A narrow callback preserves the contract.
Files & setupFrom Project: Build a Gradebook Report
Adding score strings
A type error occurs or strings concatenate instead of adding scores.
See the fix
COMMON MISTAKEtotals[name] += row['score']
FIXscore = int(row['score']); totals[name] += score
Parse and validate the numeric field before arithmetic.
Files & setupFrom Project: Build a Gradebook Report
Sorting only by average
Tie order depends on input order rather than a documented rule.
See the fix
COMMON MISTAKEsorted(rows, key=lambda row: -row['average'])
FIXsorted(rows, key=lambda row: (-row['average'], row['student']))
The second key gives deterministic ranking.
Files & setupFrom Write Clear CLI Output and Exit Codes
Printing usage to stdout and returning success
A shell script treats invalid input as successful output.
See the fix
COMMON MISTAKEprint('usage: greet --name NAME')
return 0
FIXprint('usage: greet --name NAME', file=sys.stderr)
return 2
Keep diagnostics on stderr and return a failure status.
Files & setupFrom Write Clear CLI Output and Exit Codes
Calling sys.exit inside reusable logic
A unit test or browser exercise exits instead of inspecting a return code.
See the fix
COMMON MISTAKEdef main(argv):
sys.exit(2)
FIXdef main(argv):
return 2
# at the local entry point: raise SystemExit(main(sys.argv[1:]))
Separate testable command logic from process termination.
Strings & textFrom Parse Quoted Command Input with shlex
Split loses quoted groups
Blue notebook becomes two arguments.
See the fix
COMMON MISTAKEline.split()
shlex applies shell-style quoting rules to create the intended token boundaries.
Strings & textFrom Parse Quoted Command Input with shlex
Treating tokens as executable shell input
User text can become shell syntax.
See the fix
COMMON MISTAKEsubprocess.run(line, shell=True)
FIXDispatch known commands in Python
Validate token[0] against an allowlist and never concatenate it into a shell command.
ProjectsFrom Build a Safe Text Command Menu
Changing state before validating
Bad later input can leave state partly changed.
See the fix
COMMON MISTAKEstate[name] = int(count_text)
FIXValidate, then copy and update
The original mapping remains usable after a rejected command.
ProjectsFrom Build a Safe Text Command Menu
Using shell-style text as a shell command
The menu grants unintended system operations.
See the fix
COMMON MISTAKEsubprocess.run(line, shell=True)
FIXDispatch only add, remove, show branches
Treat tokens as application data and keep the grammar explicit.
Functions & classesFrom Dispatch Functions by Type with singledispatch
Broad int check before bool
True is formatted as an integer.
See the fix
COMMON MISTAKEif isinstance(value, int): ...
FIXRegister bool separately
The most specific registered type handles booleans.
Functions & classesFrom Dispatch Functions by Type with singledispatch
Assuming multiple dispatch
The second argument's type does not select an implementation.
See the fix
COMMON MISTAKE@singledispatch def combine(left, right): ...
FIXDispatch on one explicit first argument
Use other design patterns if both argument types determine behavior.
Functions & classesFrom Cache Derived Values with cached_property
Forgotten invalidation
The old cached total remains visible.
See the fix
COMMON MISTAKEself.values.append(value)
FIXself.values.append(value); self.__dict__.pop('total', None)
Clearing the cached attribute causes recomputation on the next read.
Functions & classesFrom Cache Derived Values with cached_property
Clearing before validation
A rejected update can still alter cache state.
See the fix
COMMON MISTAKEself.__dict__.pop('total', None); self.values.append(value)
FIXValidate first, then mutate and invalidate
Keep state transitions together and predictable.
Functions & classesFrom Build Stateful Callable Objects with __call__
Shared global count
Separate counters affect one another.
See the fix
COMMON MISTAKEglobal count; count += step
Instance attributes keep state with the specific object.
Functions & classesFrom Build Stateful Callable Objects with __call__
Mutation before validation
An invalid step can corrupt state.
See the fix
COMMON MISTAKEself.value += step; validate(step)
FIXValidate, then update self.value
Rejected calls should leave the current value unchanged.
Files & setupFrom Test Binary Streams with io.BytesIO
Accepting a short payload
Truncated data looks like a complete record.
See the fix
COMMON MISTAKEreturn stream.read(size)
FIXCheck len(payload) == size
A declared length is part of the format contract.
Files & setupFrom Test Binary Streams with io.BytesIO
Decoding arbitrary bytes
A valid binary payload may trigger UnicodeDecodeError.
See the fix
COMMON MISTAKEstream.read(size).decode()
FIXReturn bytes to the caller
A separate layer can decode only when it knows the expected encoding.
Files & setupFrom Read Text Files with Explicit Encoding and Line Rules
Splitting only on LF
Windows-style lines retain '\r'.
See the fix
COMMON MISTAKEtext.split('\n')
splitlines recognizes LF, CRLF, and CR boundaries.
Files & setupFrom Read Text Files with Explicit Encoding and Line Rules
Omitting the encoding
Results depend on environment defaults.
See the fix
COMMON MISTAKEopen(path).read()
FIXopen(path, encoding='utf-8', newline=None)
An explicit encoding makes the data contract portable.
ProjectsFrom Build an Offline Duplicate File Scanner
Discarding the path labels
The result cannot say which files match.
See the fix
COMMON MISTAKElen(set(files.values()))
FIXGroup paths by content
Keep the human-readable identifiers alongside candidate digests.
ProjectsFrom Build an Offline Duplicate File Scanner
Treating digest equality as certain equality
A collision could create a false duplicate.
See the fix
COMMON MISTAKEif hash_a == hash_b: duplicate
FIXConfirm the original bytes match
Hashes narrow candidate sets; an exact byte comparison confirms a group.
Files & setupFrom Find Month Boundaries with calendar.monthrange
Hard-coded February
Leap-year reports omit February 29.
See the fix
COMMON MISTAKEdate(year, 2, 28)
FIXcalendar.monthrange(year, month)[1]
Ask the calendar for the actual number of days.
Files & setupFrom Find Month Boundaries with calendar.monthrange
Accepting bool as an int
True can masquerade as month one.
See the fix
COMMON MISTAKEisinstance(month, int)
This input contract excludes boolean values.
Files & setupFrom Parse and Format Timestamps with datetime
Assuming parsing enforces exact width
A non-padded month may be accepted.
See the fix
COMMON MISTAKEdatetime.strptime('2026-1-02 09:05', pattern)
FIXValidate shape with re.fullmatch first
Keep a strict input contract separate from semantic date validation.
Files & setupFrom Parse and Format Timestamps with datetime
Labelling local time as UTC
The instant becomes misrepresented.
See the fix
COMMON MISTAKElocal.replace(tzinfo=timezone.utc)
FIXAttach the true source zone, then astimezone(timezone.utc)
This lesson's replace is valid only because the input was defined as UTC.
Logic & loopsFrom Build a Python Habit Streak Analyzer
Trusting log order
An imported old entry breaks a real streak.
See the fix
COMMON MISTAKEfor old, new in zip(days, days[1:])
FIXdays = sorted(set(days))
Normalize chronology and collapse repeated check-ins before comparing neighbors.
Logic & loopsFrom Build a Python Habit Streak Analyzer
Counting day numbers
Month and leap-year boundaries fail.
See the fix
COMMON MISTAKEnew.day == old.day + 1
FIXnew - old == timedelta(days=1)
Calendar date subtraction handles transitions between months and years.
Logic & loopsFrom Calculate Quantiles and Percentiles with Python
Assuming the default is inclusive
Q1 is 15.0 rather than the expected inclusive 20.0.
See the fix
COMMON MISTAKEquantiles(values, n=4)
FIXquantiles(values, n=4, method='inclusive')
State the interpolation convention explicitly when an exact result matters.
Logic & loopsFrom Calculate Quantiles and Percentiles with Python
Treating a tiny sample as conclusive
A summary suggests more evidence than the sample contains.
See the fix
COMMON MISTAKEreport one observation's quartiles as stable
FIXreject fewer than two observations and report sample size
An arithmetic output is not a substitute for context about how much data was observed.
Logic & loopsFrom Use Weighted Random Sampling in Python
Ignoring the weights
A zero-weight item can still be selected.
See the fix
COMMON MISTAKErng.choice(items)
FIXrng.choices(items, weights=weights, k=1)[0]
choices accepts relative weights; choice selects uniformly.
Logic & loopsFrom Use Weighted Random Sampling in Python
Accepting invalid totals
There is no positive probability mass to sample.
See the fix
COMMON MISTAKErng.choices(items, weights=[0,0], k=1)
FIXvalidate all weights and require sum(weights) > 0
Input validation makes the statistical contract explicit and error messages useful.
Functions & classesFrom Build a Python Survey Response Summarizer
Indexing an optional key
A skipped answer raises KeyError.
See the fix
COMMON MISTAKErow['choice']
Missing survey answers are expected input, not exceptional corruption.
Functions & classesFrom Build a Python Survey Response Summarizer
Mixing missing with zero
Unrated responses lower the average or an empty export divides by zero.
See the fix
COMMON MISTAKEsum(ratings) / len(rows)
FIXmean(ratings) if ratings else None
The denominator should count valid observed ratings only.
Logic & loopsFrom Understand Python Algorithmic Complexity with Measured Inputs
Counting lines instead of work
The model hides a scan over many list elements.
See the fix
COMMON MISTAKEreturn (1, 1) because each search is one expression
FIXincrement a counter for every inspected element
Separate a source-code statement from the operations it performs.
Logic & loopsFrom Understand Python Algorithmic Complexity with Measured Inputs
Binary search on unsorted data
The target may be discarded despite being present.
See the fix
COMMON MISTAKEdiscard half of an arbitrary list
FIXstate and preserve sorted-input precondition
The direction of each bound change depends on ordering.
Logic & loopsFrom Solve Sorted-List Pair Sums with Two Pointers
Moving the wrong pointer
The sum decreases and a valid pair is skipped.
See the fix
COMMON MISTAKEif total < target: right -= 1
FIXif total < target: left += 1
On sorted input the left pointer raises the smallest remaining addend.
Logic & loopsFrom Solve Sorted-List Pair Sums with Two Pointers
Reusing one item
A single 4 appears to satisfy target 8.
See the fix
COMMON MISTAKEwhile left <= right
A pair requires two distinct list positions.
Logic & loopsFrom Find Combinations with Python Backtracking and Pruning
Saving a shared mutable path
Saved combinations turn into empty lists after backtracking.
See the fix
COMMON MISTAKEresults.append(path)
FIXresults.append(tuple(path))
A tuple snapshots the chosen values at that moment.
Logic & loopsFrom Find Combinations with Python Backtracking and Pruning
Pruning without a precondition
A smaller later candidate may be skipped.
See the fix
COMMON MISTAKEbreak on any candidate above remaining in unsorted input
FIXsort distinct positive candidates before pruning
Only ordered positive candidates support this branch elimination.
Files & setupFrom Validate JSON from HTTP Responses in Python
Decoding every response
An HTML error page raises JSONDecodeError or a JSON error is treated as data.
See the fix
COMMON MISTAKEpayload = json.loads(body) before checking status
FIXif status != 200: raise ValueError('unexpected status')
Transport status and payload syntax are separate checks.
Files & setupFrom Validate JSON from HTTP Responses in Python
Assuming decoded data has the right shape
A list or missing field crashes later or produces misleading data.
See the fix
COMMON MISTAKEreturn payload['name']
FIXvalidate dict, id type, and nonempty name
JSON parsing does not enforce the application's record schema.
Files & setupFrom Retry Transient Failures with Timeouts and Backoff in Python
Retrying every exception
Invalid input and programming bugs are hidden and repeated.
See the fix
COMMON MISTAKEexcept Exception: continue
FIXexcept (TimeoutError, ConnectionError)
State which failures might improve on a later attempt.
Files & setupFrom Retry Transient Failures with Timeouts and Backoff in Python
Sleeping after the final failure
The caller waits even though no next call will occur.
See the fix
COMMON MISTAKEwait(delay) before checking attempt limit
FIXif final_attempt: raise; else: wait(delay)
A delay is useful only before another attempt.
ProjectsFrom Build an Offline Python API Client Simulator
Caching an error response
A temporary 503 becomes a permanent cached error.
See the fix
COMMON MISTAKEself.cache[path] = self.transport(path) before status validation
FIXcache only a validated 200 user record
Cache insertion belongs after transport and schema checks.
ProjectsFrom Build an Offline Python API Client Simulator
Returning the mutable cached object
A caller changes the cached name for every later lookup.
See the fix
COMMON MISTAKEreturn self.cache[path]
FIXreturn dict(self.cache[path])
A shallow copy is enough for this flat id/name record.
Functions & classesFrom Python TypedDict: Required and Optional Keys
Treating NotRequired as nullable
A checker still expects the key to be present.
See the fix
COMMON MISTAKEnickname: str | None
FIXnickname: NotRequired[str]
A nullable value and an absent key are different contracts.
Functions & classesFrom Python TypedDict: Required and Optional Keys
Assuming a runtime validator
This constructs a dict despite the incorrect value type.
See the fix
COMMON MISTAKEprofile = Profile(id='seven')
FIXValidate untrusted input, then annotate the result as Profile
TypedDict supports static analysis but does not reject bad runtime values.
Files & setupFrom Pillow Resize Image Without Losing Aspect Ratio
Forcing the bounding-box dimensions
A 1200×800 image becomes a distorted square.
See the fix
COMMON MISTAKEimage.resize((600, 600))
FIXimage.resize((600, 400), resample=Image.Resampling.LANCZOS)
Calculate a matching size from one scale factor.
Files & setupFrom Pillow Resize Image Without Losing Aspect Ratio
Mutating the only source with thumbnail
The original object is changed in place.
See the fix
COMMON MISTAKEimage.thumbnail((600, 600))
FIXcopy = image.copy(); copy.thumbnail((600, 600))
Use a copy when the full-resolution source must remain available.
CollectionsFrom Pandas GroupBy Multiple Columns with Named Aggregations
Grouping by one column
Pen and Pad sales in the same region merge into one subtotal.
See the fix
COMMON MISTAKEsales.groupby('region').amount.sum()
FIXsales.groupby(['region', 'product']).amount.sum()
Use the complete set of dimensions that defines a bucket.
CollectionsFrom Pandas GroupBy Multiple Columns with Named Aggregations
Losing missing-key rows
Rows with a missing region or product are absent from the report.
See the fix
COMMON MISTAKEsales.groupby(['region', 'product']).amount.sum()
FIXsales.groupby(['region', 'product'], dropna=False).amount.sum()
Choose whether missing-key rows belong in reconciliation totals.
CollectionsFrom Pandas Merge on Multiple Columns Without Duplicate Matches
Joining on one of two keys
An East order receives both East and West price rows.
See the fix
COMMON MISTAKEorders.merge(pricebook, on='sku')
FIXorders.merge(pricebook, on=['sku','region'])
Use the entire composite key.
CollectionsFrom Pandas Merge on Multiple Columns Without Duplicate Matches
Trusting a left join without checks
Duplicate lookup pairs can multiply orders and unmatched rows have NaN prices.
See the fix
COMMON MISTAKEorders.merge(pricebook, how='left', on=['sku','region'])
FIXorders.merge(pricebook, how='left', on=['sku','region'], validate='many_to_one', indicator=True)
State cardinality and inspect left_only rows.
CollectionsFrom Pandas Drop Duplicates by Column and Keep the Latest Row
Dropping only full-row duplicates
Two rows with one email remain when their timestamps differ.
See the fix
COMMON MISTAKEframe.drop_duplicates()
FIXframe.drop_duplicates(subset=['email'])
Specify the column that defines duplicate identity.
CollectionsFrom Pandas Drop Duplicates by Column and Keep the Latest Row
Keeping the last input row without sorting
A newer row appearing earlier can be discarded.
See the fix
COMMON MISTAKEframe.drop_duplicates(subset=['email'], keep='last')
FIXframe.sort_values('_parsed_time', kind='stable').drop_duplicates(subset=['email'], keep='last')
Define chronological order before choosing the last row.
Files & setupFrom Python Create Temp File with NamedTemporaryFile
Reading at end-of-file
The result is an empty string.
See the fix
COMMON MISTAKEtemp.write(text)
return temp.read()
FIXtemp.write(text)
temp.seek(0)
return temp.read()
Read from the beginning of the same open handle.
Files & setupFrom Python Create Temp File with NamedTemporaryFile
Treating a temporary name as permanent
The caller receives a path that disappears after the context exits.
See the fix
COMMON MISTAKEreturn temp.name
Return the data you need before closing, or use a documented persistent destination.
Files & setupFrom Python Rotating File Handler: Limit Log File Size
Setting backups without a size threshold
The log grows without creating backups.
See the fix
COMMON MISTAKERotatingFileHandler(path, maxBytes=0, backupCount=2)
FIXRotatingFileHandler(path, maxBytes=24, backupCount=2)
Both values must enable rollover.
Files & setupFrom Python Rotating File Handler: Limit Log File Size
Adding a new handler on every call
Repeated runs can write the same record several times.
See the fix
COMMON MISTAKElogging.getLogger('app').addHandler(handler)
FIXUse one owned logger and close/remove the handler
Make the lifetime of each handler explicit.
Functions & classesFrom Python argparse Subcommands with add_subparsers
Putting every option on the parent parser
list accepts an irrelevant name and add does not require it.
See the fix
COMMON MISTAKEparser.add_argument('--name')
FIXadd_parser.add_argument('--name', required=True)
Command-specific arguments belong on that child parser.
Functions & classesFrom Python argparse Subcommands with add_subparsers
Making the command optional
Running notes with no verb can yield command=None.
See the fix
COMMON MISTAKEparser.add_subparsers(dest='command')
FIXparser.add_subparsers(dest='command', required=True)
Reject incomplete invocations at the grammar boundary.
Functions & classesFrom Python concurrent.futures as_completed: Handle Results as They Finish
Blocking in submission order
A slow first job delays handling completed later jobs.
See the fix
COMMON MISTAKEfor future in futures: print(future.result())
FIXfor future in as_completed(futures): print(future.result())
Process each job when it becomes available.
Functions & classesFrom Python concurrent.futures as_completed: Handle Results as They Finish
Forgetting the result exception
One failed job aborts the whole collection unexpectedly.
See the fix
COMMON MISTAKEvalue = future.result()
FIXtry: value = future.result()
except ValueError: value = '<error>'
Choose and document an error policy per job.
Functions & classesFrom Python subprocess.run Command: Capture Output Safely
Passing one unsplit command string
The operating system may look for a program with that entire name.
See the fix
COMMON MISTAKEsubprocess.run('python -c code Ada')
FIXsubprocess.run([sys.executable, '-c', code, 'Ada'])
Use a sequence to identify executable and arguments precisely.
Functions & classesFrom Python subprocess.run Command: Capture Output Safely
Interpolating data into shell syntax
User-controlled text can change what the shell executes.
See the fix
COMMON MISTAKEsubprocess.run(f'python child.py {name}', shell=True)
FIXsubprocess.run([sys.executable, 'child.py', name])
Keep untrusted text in an argv element and avoid an unnecessary shell.
Files & setupFrom Python zipfile extract: unpack selected files safely
Extracting everything
Unexpected members are written beside the intended text files.
See the fix
COMMON MISTAKEarchive.extractall(destination)
FIXInspect infolist(), enforce a policy, then write accepted members.
The application decides which files belong in its destination.
Files & setupFrom Python zipfile extract: unpack selected files safely
Checking after a write
A failed operation leaves partial output.
See the fix
COMMON MISTAKEwrite the first file, then reject a later path
FIXValidate every member before creating destination files.
A validation pass makes rejection predictable.
Files & setupFrom FastAPI request body: validate JSON with a Pydantic model
Using an untyped dict as the entire contract
The API does not document the expected fields and limits as clearly.
See the fix
COMMON MISTAKEdef create_item(item: dict): ...
FIXDeclare a BaseModel with required typed fields and constraints.
FastAPI can then validate the body and publish its schema in OpenAPI docs.
Files & setupFrom FastAPI request body: validate JSON with a Pydantic model
Assuming every integer-like value is acceptable
A boolean can be treated as quantity one in handwritten checks.
See the fix
COMMON MISTAKEisinstance(True, int)
FIXState the chosen coercion policy; use strict constraints when needed.
Type coercion and business rules deserve an explicit decision.
Files & setupFrom BeautifulSoup parse table: extract HTML rows into Python records
Selecting every row on the page
Rows from a second table mix into the result.
See the fix
COMMON MISTAKEsoup.find_all('tr')
FIXSelect the intended table first, then its rows.
Scope a query to the table that carries the data you need.
Files & setupFrom BeautifulSoup parse table: extract HTML rows into Python records
Silently truncating ragged rows
A missing cell disappears from the record without warning.
See the fix
COMMON MISTAKEdict(zip(headers, cells))
FIXCompare header and cell counts before zipping.
A predictable extraction contract makes later analysis reliable.
Files & setupFrom Python cProfile example: find slow functions and read pstats
Reading cumulative time as self time
A wrapper function is blamed for work in its subcalls.
See the fix
COMMON MISTAKEcumtime == time spent only in this function's own lines
FIXCompare cumtime with tottime and inspect callees.
A profiler report separates inclusive and exclusive time.
Files & setupFrom Python cProfile example: find slow functions and read pstats
Profiling an unrepresentative input
An algorithm that scales poorly appears harmless.
See the fix
COMMON MISTAKEprofile a list of three items and extrapolate
FIXProfile representative inputs and compare under the same conditions.
Input size and shape affect where time is spent.
Files & setupFrom openpyxl read Excel in Python: rows, headers, and values
Assuming the first row is correct
A renamed column silently changes every record key.
See the fix
COMMON MISTAKEdict(zip(rows[0], row))
FIXValidate the expected header tuple before mapping rows.
Data contracts should fail visibly when the sheet layout changes.
Files & setupFrom openpyxl read Excel in Python: rows, headers, and values
Treating formula cache as calculation
Formula cells may be None or stale.
See the fix
COMMON MISTAKEload_workbook(path, data_only=True) # computes formulas
FIXUse cached values only after a spreadsheet application has recalculated and saved the workbook.
openpyxl reads a workbook; it is not a formula engine.
CollectionsFrom NumPy reshape: change array dimensions without shape errors
Reversing rows and columns
The number supplied becomes the row count instead of the requested column count.
See the fix
COMMON MISTAKEarray.reshape(columns, -1)
FIXarray.reshape(-1, columns)
The last dimension is the number of entries per row.
CollectionsFrom NumPy reshape: change array dimensions without shape errors
Expecting reshape to resize data
ValueError because the requested shape requires eight elements.
See the fix
COMMON MISTAKEnp.arange(6).reshape(4, 2)
FIXnp.arange(6).reshape(2, 3)
Reshape preserves the total element count.
CollectionsFrom Pandas pivot table with multiple aggregations
Using pivot on duplicate pairs
ValueError: duplicate entries cannot be reshaped.
See the fix
COMMON MISTAKEsales.pivot(index='region', columns='product', values='amount')
FIXpd.pivot_table(..., aggfunc=['sum', 'mean'])
A transaction table needs an aggregation policy for repeated pairs.
CollectionsFrom Pandas pivot table with multiple aggregations
Filling an absent mean with zero
A missing observation can look like a real mean of zero.
See the fix
COMMON MISTAKEpd.pivot_table(..., aggfunc=['sum','mean'], fill_value=0)
FIXKeep NaN until the reporting policy for missing cells is decided.
Missing data and observed zero answer different questions.
CollectionsFrom Pandas fillna: different values for different columns
One scalar for mixed column types
The city column gets a numeric zero instead of a meaningful label.
See the fix
COMMON MISTAKEframe.fillna(0)
FIXframe.fillna({'city': 'Unknown', 'score': 0})
Choose replacements that match each field's meaning and type.
CollectionsFrom Pandas fillna: different values for different columns
Confusing blank strings with missing data
A city value of '' remains blank.
See the fix
COMMON MISTAKEframe.fillna({'city':'Unknown'})
FIXNormalize blank strings deliberately before fillna if the data contract calls for it.
fillna operates on recognized null markers.
Logic & loopsFrom asyncio.gather: run tasks together and handle errors
Serial awaiting
Independent waits run one after another.
See the fix
COMMON MISTAKE[await job(x) for x in inputs]
FIXawait asyncio.gather(*(job(x) for x in inputs))
Submit the independent awaitables before waiting for their collection.
Logic & loopsFrom asyncio.gather: run tasks together and handle errors
Treating exceptions as strings
With return_exceptions=True, a result can be an exception object.
See the fix
COMMON MISTAKEresult.upper()
FIXCheck isinstance(result, ValueError) before formatting it.
Keep expected failure handling explicit and leave unexpected exceptions visible.
Functions & classesFrom pytest.mark.parametrize: test many cases with one function
Copying nearly identical tests
Each new input requires another duplicated function.
See the fix
COMMON MISTAKEtest_zero(), test_twelve(), test_spaces() with repeated bodies
FIX@pytest.mark.parametrize('text, expected', rows)
Keep one assertion body and enumerate varied inputs as rows.
Functions & classesFrom pytest.mark.parametrize: test many cases with one function
Only checking successful values
Negative and malformed input behavior stays untested.
See the fix
COMMON MISTAKErows = [('1', 1), ('2', 2)]
FIXAdd separate parametrized invalid cases using pytest.raises(ValueError).
The error contract is part of the function's behavior.
Files & setupFrom Pandas to_datetime: Parse Dates and Find Invalid Rows
Leaving dates as strings
Lexical order can differ from chronological order.
See the fix
COMMON MISTAKEsort_values('date_text') on mixed formats
FIXpd.to_datetime(source, format='%Y-%m-%d')
Parse according to a documented source format before date operations.
Files & setupFrom Pandas to_datetime: Parse Dates and Find Invalid Rows
Dropping bad rows silently
Invalid source records vanish without an error report.
See the fix
COMMON MISTAKEpd.to_datetime(values, errors='coerce').dropna()
FIXparsed = pd.to_datetime(values, errors='coerce'); bad = parsed.isna()
Preserve source row identity before filtering.
Files & setupFrom Matplotlib savefig: Export a PNG Without Clipped Labels
Saving the wrong active figure
An empty or different chart is exported.
See the fix
COMMON MISTAKEplt.savefig('chart.png') after creating another figure
FIXfig.savefig('chart.png')
Keep and save the intended Figure object.
Files & setupFrom Matplotlib savefig: Export a PNG Without Clipped Labels
Assuming tight crop preserves exact pixels
The resulting dimensions differ from the assumed canvas.
See the fix
COMMON MISTAKEfig.savefig(path, dpi=200, bbox_inches='tight') # always 1200x600
FIXInspect the saved dimensions if exact pixels matter.
Bounding-box cropping changes the output canvas.
Strings & textFrom Python re.findall: Return Full Matches Instead of Groups
Capturing the prefix accidentally
The list contains ERR and WARN rather than full codes.
See the fix
COMMON MISTAKEre.findall(r'(ERR|WARN)-[0-9]{3}', text)
FIXre.findall(r'(?:ERR|WARN)-[0-9]{3}', text)
Noncapturing parentheses retain alternation without changing the return shape.
Strings & textFrom Python re.findall: Return Full Matches Instead of Groups
Matching an overlong code prefix
ERR-101 appears even though the whole code has four digits.
See the fix
COMMON MISTAKEre.findall(r'ERR-[0-9]{3}', 'ERR-1012')
FIXre.findall(r'\bERR-[0-9]{3}\b', text)
A boundary after the digits rejects a longer word token.
Functions & classesFrom Pydantic field_validator: Normalize and Validate a Field
Assuming before means string
A number or None raises AttributeError inside the validator.
See the fix
COMMON MISTAKEvalue.strip()
FIXCheck isinstance(value, str) before strip.
A before validator sees raw input.
Functions & classesFrom Pydantic field_validator: Normalize and Validate a Field
Forgetting to return the cleaned value
Validation receives None rather than a normalized handle.
See the fix
COMMON MISTAKEvalue.strip().casefold() # no return
FIXreturn value.strip().casefold()
Validator output becomes the field value for later validation.
Functions & classesFrom FastAPI Query Parameters: Validate skip and limit
Ignoring the offset
Every page begins at the first item.
See the fix
COMMON MISTAKEreturn items[:limit]
FIXreturn items[skip:skip + limit]
Slice start and stop must both reflect the offset.
Functions & classesFrom FastAPI Query Parameters: Validate skip and limit
Leaving page size unbounded
A caller can request an excessive response size.
See the fix
COMMON MISTAKEdef list_items(limit: int = 10): ...
FIXlimit: int = Query(default=10, ge=1, le=50)
A bounded HTTP parameter makes the contract explicit and documented.
Files & setupFrom pytest tmp_path: test file-writing code without touching real files
Writing into the repository
Tests depend on the working directory and can overwrite a real file.
See the fix
COMMON MISTAKEPath('report.txt').write_text(data)
FIXPass tmp_path to the writer and build tmp_path / 'report.txt'.
A destination parameter makes the file side effect testable and isolated.
Files & setupFrom pytest tmp_path: test file-writing code without touching real files
Checking only a return value
The test may pass without confirming the saved content.
See the fix
COMMON MISTAKEassert save_report(tmp_path, lines).name == 'report.txt'
FIXRead the returned path with encoding='utf-8' and compare the text.
The disk content is the behavior this test promises.
Functions & classesFrom Dataclass to dict in Python: asdict, nested objects, and copy behavior
Using __dict__ as recursive conversion
Nested Customer is still a Customer object and slotted dataclasses may lack __dict__.
See the fix
COMMON MISTAKEorder.__dict__
FIXUse asdict(order) for recursive dataclass conversion.
asdict follows declared fields and converts nested dataclasses.
Functions & classesFrom Dataclass to dict in Python: asdict, nested objects, and copy behavior
Assuming JSON compatibility
JSON encoding raises TypeError for unsupported values.
See the fix
COMMON MISTAKEjson.dumps(asdict(record_with_datetime))
FIXConvert special values explicitly before json.dumps, or supply a deliberate serializer.
A Python dict is not automatically valid JSON data.
Functions & classesFrom jsonschema validate Python data: required fields and readable errors
Forgetting required
An object without age may still validate.
See the fix
COMMON MISTAKE{'properties': {'age': {'type': 'integer'}}}
FIXAdd 'required': ['age'] when age must be present.
properties and required have separate roles in JSON Schema.
Functions & classesFrom jsonschema validate Python data: required fields and readable errors
Treating validation as coercion
The string fails the integer rule.
See the fix
COMMON MISTAKEvalidator.validate({'age': '20'}) # expects 20
FIXParse or reject the string before validation according to your input contract.
jsonschema checks the instance; it does not transform a value into another type.
Files & setupFrom Python CSV to JSON: convert rows with DictReader and json.dumps
Serializing Python repr instead of JSON
The text uses Python quotes and is not guaranteed valid JSON.
See the fix
COMMON MISTAKEreturn str(list(csv.DictReader(stream)))
FIXPass the list of row dictionaries to json.dumps.
JSON has its own serialization rules and escaping.
Files & setupFrom Python CSV to JSON: convert rows with DictReader and json.dumps
Silently accepting a ragged row
Extra cells may appear under a None key; missing cells may become null.
See the fix
COMMON MISTAKErows = list(csv.DictReader(stream)); json.dumps(rows)
FIXCheck headers and each row before serializing.
A predictable output schema depends on validating the CSV structure.
CollectionsFrom NumPy boolean indexing: filter arrays with masks and avoid copy surprises
Trying to mutate a selected copy
The original values array remains unchanged.
See the fix
COMMON MISTAKEselected = values[mask]; selected[0] = 0
FIXAssign through values[mask] = 0 when the source should change.
Boolean selection returns a separate array.
CollectionsFrom NumPy boolean indexing: filter arrays with masks and avoid copy surprises
Combining masks with and
NumPy cannot interpret the truth value of a multi-element array.
See the fix
COMMON MISTAKEvalues > 5 and values < 20
FIXUse (values > 5) & (values < 20).
The ampersand combines the Boolean arrays element by element.