Naming things, which is most of the job
You will spend far more time reading code than writing it — your own included, six months later, with no memory of writing it. A name is the interface between what the code does and what the next person believes it does, and when those drift apart, that is where bugs live.
Python makes this more important than most languages, because there are no type
declarations to fall back on. In Java, List<Delivery> x tells you something
even with a terrible name. In Python, x tells you nothing at all.
PEP 8, which is not optional
| Thing | Convention | Example |
|---|---|---|
| Variable, function, method | snake_case |
total_paise, load_expenses |
| Class | CapWords |
ExpenseTracker |
| Constant | UPPER_SNAKE_CASE |
MAX_ENTRIES |
| Module, package | short lowercase |
storage.py, reports |
| "Internal" | one leading underscore | _cache, _normalise |
| Name-mangled | two leading underscores | __slots — rarely what you want |
| Avoiding a keyword | one trailing underscore | class_, id_ |
These are not matters of taste. Every Python codebase you join follows them, Ruff enforces them, and departing marks the code as written by somebody who did not know. Module 10 covered running Ruff; this is what it is checking against.
Two specifics worth calling out:
Never use l, O or I as single-character names. In most fonts l is
indistinguishable from 1 and O from 0. PEP 8 says this explicitly, and it
is the only naming rule that exists purely because of typography.
One leading underscore is a convention, not protection. _cache means "this
is internal, do not touch". Nothing stops you. Python trusts you and expects you
to read.
Say what, not how
# how
def filter_list_by_date_using_loop(items, month):
...
# what
def expenses_in(month):
...
The second survives changing the implementation. The first is a comment about the current body, written where a comment cannot be ignored, and it becomes a lie the moment somebody rewrites it as a comprehension.
Length should match scope
for e in expenses:
total += e.amount_paise
e is fine — it is born and dies within two lines. But a name that lives
longer has to carry more:
self.expenses_by_category = {} # good
self.d = {} # not
The further a name travels, the more it has to carry. A comprehension variable, one letter. A module-level constant, a full phrase.
Put the unit in the name
The single highest-value naming habit in this course:
amount_paise = 28_500
timeout_seconds = 30
file_size_bytes = 1_048_576
amount alone raises a question the reader must answer from somewhere else —
rupees or paise? amount_paise cannot be misread, and the day somebody writes
amount_paise = 285 meaning ₹285, the name is standing right there arguing with
them.
Python's 1_000_000 underscores help too. 28_500 is obviously twenty-eight
and a half thousand; 28500 needs counting.
Booleans read as questions
if expense.is_recurring:
...
if tracker.has_entries():
...
if user.can_edit(entry):
...
is, has, can, should. The test is whether the if reads as English.
And avoid negatives — if not entry.is_not_valid is two negatives and one
confused reader.
Functions that return a boolean, and functions that do a thing
def validate(entry): # does it return something? raise? both?
...
def is_valid(entry): # returns a bool
...
def validate_or_raise(entry): # raises
...
Python has no return-type declaration in the signature unless you add a hint, so the name is carrying that information. This matters more here than in a typed language.
Say no to the noise words
expense_data # as opposed to an expense that is not data?
expense_info
expense_object
process_expense # "process" means nothing
handle_expense
do_save()
manage_expenses()
manager, helper, util, data, info, process, handle. If removing
the word does not change the meaning, remove it.
The exception is a real convention: ExpenseStore says where expenses live.
ExpenseHelper says nothing.
A function you cannot name is usually a function doing more than one thing. That is a design problem surfacing as a naming problem, and it is worth listening to.
Be consistent about one idea
Pick one word per concept:
# Pick one:
get_expense / fetch_expense / load_expense / read_expense
Two words for the same thing makes a reader search for a distinction that is
not there. Two words for genuinely different things is right — the capstone uses
entry for one row and expense for the concept, and those differ.
Python's own traps
Do not shadow builtins.
list = [1, 2, 3] # now list() is gone for the rest of this scope
dict = {}
type = "recurring"
id = 5
input = "..."
This is the most common Python naming bug, and the error it produces later —
TypeError: 'list' object is not callable — points at the line that uses
list(), nowhere near the line that broke it. Use items, mapping,
kind, identifier, text.
_ means deliberately unused.
for _ in range(3):
print("hello")
name, _, amount = line.split(",")
It tells the reader "I know there is a value here and I do not want it".
Module names are short and lowercase. expense_store.py, not
ExpenseStore.py or expensestore.py. A capitalised module name is a common
giveaway of somebody arriving from Java.
Names as a design signal
Naming difficulty is information:
- "I cannot name this function" — it probably does two things.
- "I need
andin the name" —save_and_notifyis two functions. - "I keep writing
manager" — the responsibility is not clear yet. - "The name is very long" — either it does too much, or it belongs on a different class where the context is implied.
# hard to name
def process_expense_and_update_totals_and_save(entry): ...
# easy to name
def add(entry): ...
def recalculate_totals(): ...
def save(): ...
Check your work
Why names matter more in Python: there are no type declarations to fall back on, so the name carries information a signature would carry elsewhere.
Why PEP 8 is not taste: every codebase and every tool assumes it.
Why not l, O, I: in most fonts they are indistinguishable from 1 and
0.
What one leading underscore means: a convention saying "internal", not protection.
Why name what, not how: a name describing the implementation becomes a lie when the implementation changes.
The highest-value habit: put the unit in the name — amount_paise,
timeout_seconds.
How booleans should read: as a question, with is, has, can, should.
Why shadowing builtins is the classic bug: the error appears at the line
that uses list(), far from the line that broke it.
What _ communicates: a value deliberately ignored.
What naming difficulty tells you: a function you cannot name does two things.
Practice
- Open your capstone and find the three worst-named things. Rename them.
- Find every variable holding money. Confirm each has
_paisein its name. - Write
list = [1, 2, 3]then calllist("abc")and read the error. Note how far the message is from the cause. - Find a function whose name starts with
process,handleordo. Rename it to what it actually does. - Find a boolean-returning function not starting with
is,has,canorshould. Read a call site aloud. - Search your code for
data,info,manager,helper,util. Try deleting the word from each. - Find two words used for the same concept. Pick one.
- Run Ruff on your capstone and look only at the naming complaints.
- Find a function with
andin its name and split it. - Ask somebody to guess what one of your functions does from its name alone.
Next: how big is too big.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship