Best Practices and Common Mistakes
Anyone can cook a meal once. A good cook leaves a kitchen the next cook can walk into and use.
You've learned a lot of Python: values and operators, decisions and loops, functions and modules, lists, tuples, dictionaries, and sets, classes and objects, exceptions, files, and JSON. You can build real programs now.
This lesson is about building them well. Code is read far more often than it's written: by teachers, by teammates, and most of all by you, a few weeks from now, when you've forgotten how it works. Working code is the minimum. Code that's easy to read, easy to change, and hard to break is the goal.
None of this is new syntax. It's habits, collected from the whole track in one place, plus a checklist of the mistakes that trip up almost every Python beginner.
A shared kitchen
Picture a restaurant kitchen shared by several cooks across different shifts. A good cook labels every container, puts the knives back where they belong, and cleans as they go. The next cook walks in and gets straight to work.
A messy cook might make an equally good meal. But they leave unlabeled containers and a knife in the flour, and the next cook spends half their shift working out what's what. Your code is that kitchen.
PEP 8: Python's style guide
Python has an official style guide, called PEP 8, that almost every Python programmer follows. Following it makes your code instantly familiar to anyone who reads it. The most important parts:
| Kind | Style | Examples |
|---|---|---|
| variables and functions | snake_case | quiz_score, calculate_average |
| constants | UPPER_SNAKE_CASE | MAX_SCORE, PASSING_SCORE |
| classes | PascalCase | Student, BankAccount |
| modules (files) | short, lowercase | grades.py, helpers.py |
And for layout:
- Indent with 4 spaces per level.
- Keep lines reasonably short. PEP 8 suggests 79 characters, and many teams allow a bit more.
- Put a space around operators and after commas:
total = a + b, nottotal=a+b. - Leave two blank lines between top-level functions and classes, and one between methods.
- Put all
importlines at the top of the file.
You don't have to memorize any of this. Tools called formatters, like Black or Ruff, rewrite your file into this style automatically, and code editors can run them every time you save.
Name things by what they are
Beyond the style rules, choose names that explain themselves:
average_scorebeatsavg, andavgbeatsx.- Functions do things, so name them with verbs:
load_grades,print_report. - True-or-false values read like questions:
is_enrolled,has_paid. - Short names like
iorxare fine for tiny loops, where their meaning is obvious.
And never reuse the names of Python's built-in functions for your own variables. This is a surprisingly common bug:
list = [3, 1, 2]
numbers = list((5, 4)) # error: TypeError: 'list' object is not callableAfter list = [3, 1, 2], the name list means your list, not Python's list() function, so calling it fails. The same goes for str, sum, max, input, and id. Choose scores, total, or names instead.
Replace magic numbers with constants
A magic number is a number in your code with no explanation:
score = 82
if score >= 75:
print("Passed")PassedWhat's 75? The reader has to guess. And if the passing mark changes, you have to find every 75 in the program, and hope none of them meant something else. Give it a name instead, as a constant near the top of the file:
PASSING_SCORE = 75
score = 82
if score >= PASSING_SCORE:
print("Passed")PassedKeep functions small and focused
A function should do one job, and its name should say what that job is. When a function grows past a screenful, or you need the word "and" to describe it, split it up. A program's main part can then read almost like a summary:
def main():
students = load_students("students.json")
print_report(students)
save_report(students, "report.txt")You can understand the whole program in three lines, and dig into whichever function you care about.
And don't repeat yourself. If the same lines appear in several places, move them into a function. If several classes share the same code, consider a parent class from Inheritance. The habit is known as DRY: Don't Repeat Yourself.
Write Pythonic code
Experienced Python programmers talk about code being Pythonic: using the language's own tools the way they were designed. You've learned most of them already:
| Instead of | Write |
|---|---|
for i in range(len(names)): then names[i] | for name in names: |
| a counter you add to by hand, next to a loop | for i, name in enumerate(names): |
| looping over two lists by index | for name, score in zip(names, scores): |
if len(names) == 0: | if not names: |
if value == None: | if value is None: |
"Total: " + str(total) | f"Total: {total}" |
an empty list, a loop, and append | a list comprehension, when it's simple |
file = open(...) and remembering close() | with open(...) as file: |
Comment the why, not the what
Good names explain what code does. Comments are for what the code can't say: why it does it that way.
# Bad: repeats the code
count += 1 # add one to count
# Good: explains a decision
# Scores above 100 come from extra-credit work, so allow up to 110.
MAX_SCORE = 110Use docstrings from Writing Functions to describe what each function is for. And don't leave old code commented out "just in case": that's what version control is for.
Handle errors honestly
From Exceptions:
- Catch specific exceptions you have a plan for. Never use a bare
except:. - Never leave an
exceptblock that silently does nothing. - Raise a
ValueErrorwhen a function gets data it can't work with, instead of quietly "fixing" it.
The mistakes almost everyone makes
When something's wrong and you can't see why, run down this list. Every one of these came up somewhere in this track.
- Forgetting the colon after
if,for,while,def, orclass. (Making Decisions) - Mixed-up indentation, where a line meant to be inside a block isn't, or the other way around. (Making Decisions)
- Using
=instead of==in a condition. (Operators and Expressions) - Forgetting that
input()gives a string, then doing math with it. (Reading User Input) - Expecting a string method to change the string.
name.upper()on its own does nothing; writename = name.upper(). (Working with Strings) - Assigning the result of a list method, like
names = names.append("Eli"), which replaces your list withNone. (Lists) - Thinking
b = acopies a list. It's a second name for the same list; usea.copy(). (Lists) - Off-by-one ranges, forgetting that
range(1, 10)stops at 9. (for Loops and range()) - Forgetting the
fbefore an f-string, so the braces print as-is. (Formatting Output with f-strings) - A list as a default value, like
def add(item, items=[]), shared between calls. (Default and Keyword Arguments) - Forgetting
self, either as a method's first parameter or in front of an attribute. (Methods and self) - Comparing floats with
==.0.1 + 0.2 == 0.3isFalse; usemath.isclose(a, b)orround. (Numbers, Strings, and Booleans) - Naming your own file after a module, like
random.py, so your import finds your file instead of Python's. (Modules and import)
Try it
This program works. It runs and prints the right answer. But it breaks almost every habit in this lesson. Read it, predict what it prints, and count how many problems you can spot along the way.
Running Python here
Running Python right in the browser is coming to Syntaxia soon. For now, you can read and edit the code here. If you have Python installed on your computer (see Setting Up), save it as main.py and run it with python main.py (or python3 main.py on macOS and Linux).
Check your prediction
Maria passed with 91
Ben needs a retake (68)
Carlo passed with 95
Dina needs a retake (74)
Passed: 2/4
Avg: 82.0Some of the problems:
- names like
a,b,c, andtsay nothing; - the magic numbers
75and4are repeated, and the4breaks the moment a student is added; - the same condition is checked three times, and two
ifblocks are crammed onto one line each; - names and scores live in two lists that only line up by luck, and
range(len(...))is used instead ofzip; +andstr()are used where an f-string would be clearer, andsumis rewritten by hand.
Here's one way to clean it up. It prints exactly the same thing:
PASSING_SCORE = 75
students = [("Maria", 91), ("Ben", 68), ("Carlo", 95), ("Dina", 74)]
def print_result(name, score):
if score >= PASSING_SCORE:
print(f"{name} passed with {score}")
else:
print(f"{name} needs a retake ({score})")
for name, score in students:
print_result(name, score)
scores = [score for _, score in students]
passed = sum(1 for score in scores if score >= PASSING_SCORE)
print(f"Passed: {passed}/{len(students)}")
print(f"Avg: {sum(scores) / len(scores)}")It's a little longer, and that's fine. Every piece now has a name that says what it does, each name stays next to its score, adding a fifth student is one line, and changing the passing mark is a single edit.
Try it yourself
- In the messy version, add a fifth student,
Eli, with a score of 88. How many places did you have to change? Now do the same in the clean version. - In the clean version, change the passing score to 70. Which students' results change?
- Pick a program you wrote earlier in this track. Find one magic number, one unclear name, and one place you could use a more Pythonic tool from the table above, and fix all three.
Check your understanding
Up next
That's everything. You have the whole toolkit, and the habits to use it well. Time to put it all together in one real program: Final Project: Personal Budget Tracker.
