Embercode
✕
Variables and Data TypesLesson 16 of 16

40 min read

Naming and reading declarations in Python

Why it matters

In Python, a declaration is the line that gives a name its first value. In a type-hinted program, the line can also state the type of the value:

Python · editable
12

The line says three things. The type is int, the name is student_count, and the starting value is 12. Many lines in Python do not state the type, such as student_count = 12, but many programs you will read use type hints.

Names matter for a second reason. Python does not care whether a name says what the value means. It accepts x = 12 and student_count = 12 in the same way. People do care. A name like x makes the reader stop and work out the meaning. A name like student_count tells them at once. Clear names make bugs easier to spot.

This lesson has two parts. The first covers how to name variables and constants, which is partly a rule for the interpreter and partly a convention for people. The second covers how to read a type hint from left to right, including the forms you will meet in this course: lists, dictionaries and lists inside dictionaries. The lesson does not repeat the basic types from the variables lessons.

Part 1: naming

The rules the interpreter enforces

A name is called an identifier. Python accepts an identifier only if it follows these rules:

  • It uses letters, digits and the underscore _. It does not use the dollar sign or the hyphen.
  • It does not start with a digit.
  • It is not a keyword. A keyword is a word Python already uses for its own grammar, such as class, for, if, return, def and None. The full list is in the keyword module.
  • Upper case and lower case letters are different. total and Total are two different names.

Letters outside plain English are also allowed. The next program checks the keyword list and uses a name with an accented letter:

Python · editable
True False False
1

The words print and int are not keywords. They are names of built-in functions and types. Python lets you reuse them, which is a trap that the mistakes section shows. This course asks you to use English letters, digits and _ only, so the code reads the same on every machine and in every editor.

The rules produce errors that beginners see most often. Here is a file with a name that starts with a digit:

2fast = 3
  File ".../digit.py", line 1
    2fast = 3
    ^
SyntaxError: invalid decimal literal

Python reads 2 as the start of a number, and the letters that follow are not valid in a number. The fix is to rename the variable, for example fast_count. A hyphen causes a different message:

my-score = 4
  File ".../hyphen.py", line 1
    my-score = 4
    ^^^^^^^^
SyntaxError: cannot assign to expression here. Maybe you meant '==' instead of '='?

Python reads my-score as my minus score, which is not something you can assign to. The caret covers the whole expression on the left of =. Use my_score instead.

A keyword cannot be a name. Writing class = 5 gives a single message:

class = 5
  File ".../keyword.py", line 1
    class = 5
          ^
SyntaxError: invalid syntax

The caret points at the =, which is the first place where Python expected a name and did not find one. Keyword names are the usual cause of a confusing message in an assignment. If the name looks right, check whether it is a keyword.

The style people follow

The interpreter does not check the next rules. They are conventions in Python's style guide, called PEP 8, and most Python code follows them.

  • Variables and functions use lower case letters, with an underscore between words. This is called snake_case. Examples: student_count, average_score, find_largest.
  • Classes start with a capital letter, and each new word starts with a capital. This is called PascalCase. Examples: Main, ShoppingCart, LinkedList.
  • Constants use capital letters with underscores between words. A constant is a value that is meant never to change. Examples: MAX_STUDENTS, TAX_RATE.

A variable name should say what the value means, not what type it has. score is better than int_score. A short name is fine when the meaning is obvious from a small scope. The loop counter i is the usual example. A long-lived value needs a name that explains itself.

Compare these two versions of the same calculation. Both run and print the same output.

Python · editable
Python · editable

Both print 21.583333333333332. The second version tells you what average_temp is without reading the arithmetic. When the program later grows to ten values, the names keep the meaning clear.

Constants

Python has no keyword for a constant. The convention is to write the name in capital letters, with underscores between words, and to set it once at the top of the module. The interpreter does not stop a later assignment, as the variables lesson showed. A type checker such as mypy can report it when the name is declared with typing.Final.

Python · editable
Riverside has room for 18 more.

The capital letters tell the reader that the value is fixed. A reader who sees MAX_STUDENTS does not look for a line that changes it, and a reader who sees max_students might.

A good sample of names

The next program uses names that follow every rule above. It also contains several kinds of names, so you can check each one. The leading underscore on _retries is allowed, and Python uses it to mean "for internal use". The course uses it only where the meaning is that.

Python · editable
12 3
30 Riverside
3 2 3
Ravi 0 {'odd': [1, 3]}
0.18 2

Every name in that program follows the style rules. The reading of each one is in the second part of this lesson. The line index = defaultdict(list) makes a dictionary that gives each missing key an empty list, and [[0] * 3 for _ in range(2)] builds two rows of three zeros.

Part 2: reading type hints aloud

The basic pattern

A type hint is written after the name, with a colon between them. Read it from the name outward. First say the name, then "is a", then the type. A hint is read the same way whether it is a number, a list or a dictionary.

  • student_count: int reads as "student_count is an int".
  • tax_rate: float reads as "tax_rate is a float".
  • names: list[str] reads as "names is a list of str". The square brackets after list mean "items of".

The reading order follows the code. The name comes first, so you say it first. The brackets are part of the type, so they belong to the same phrase.

A type hint does not change what the program does. Python ignores it when it runs the code. A checker such as mypy reads the hints and reports a mismatch before the program runs. The next example shows that the interpreter does not check the hint at all:

Python · editable
not checked at run time

The program runs, and the hint is wrong. This is why the hint is most useful to a reader and to a checker, and the program itself still needs tests.

Lists

A list holds items in order. The hint gives the type of each item inside the brackets.

  • scores: list[int] reads as "scores is a list of int".
  • grid: list[list[int]] reads as "grid is a list of lists of int". It is a table with rows and columns. The first index picks the row, and the second index picks the column.
  • letters: list[str] reads as "letters is a list of str".

The lengths are part of the list, not the declaration. The program above prints len(grid[0]) as 3, because the list grid has two rows of three values each.

Several names on one line

Python allows several names on one line in two ways, and they behave differently. Read them carefully, because the second form creates a trap.

Python · editable
1 [4, 5]
[6] [6] True

The first line uses tuple unpacking, which gives each name one value from a group on the right. Read x, y = 1, [4, 5] as "x is 1, and y is the list [4, 5]". Each name gets its own value.

The second line is the trap. Read p = q = [] as "p and q both refer to one new empty list". The two names point to the same list, so the append through p changes what q sees. The output shows True for p is q. To give each name its own list, write two lines, p = [] and q = []. The same rule applies to any value that can change in place, such as a dictionary.

Dictionaries

A dictionary pairs each key with a value. The hint gives the type of the key and the type of the value, in that order, inside the brackets.

  • counts: dict[str, int] reads as "counts is a dictionary from str to int". The keys are strings, and each key holds a whole number.
  • index: dict[str, list[int]] reads as "index is a dictionary from str to a list of int". The value is itself a list, and that list holds whole numbers.

Read from the outside in, left to right, and add each inner part when you reach it. Here is how the last one reads as a sentence:

  1. "index is a dictionary," since the outermost type is dict.
  2. "from str to," because str is the first type inside the brackets.
  3. "a list of int," because the second type inside is list[int].

The program in the first part prints that dictionary as {'odd': [1, 3]}. The key 'odd' maps to the list [1, 3], which holds two integers.

Method headers

A function header uses the same pattern, read in pieces. The parameter hints come inside the brackets after the name, and the return hint comes after an arrow ->.

Python · editable
[9, 7]

Read the header def two_largest(values: list[int]) -> list[int]: in four parts. "two_largest is a function." It is called with one argument, which is "values, a list of int." It returns "a list of int," because the return hint comes after the arrow. The name two_largest is snake_case, as the style rules say.

The function is defined with def, and it is called by its name with no object needed. A later lesson covers functions in full.

Example 1: rename a bad program

Here is a short program written with weak names. Read it through once, and try to say what each value holds.

Python · editable

The output is 75.0. Reading it is hard, because each name says nothing about the value. Now the same program with names that say what each value is:

Python · editable

The output is still 75.0. The second version tells you that average_score is the total divided by three. It also shows what the 3 is: the number of subjects. The / gives a float, so the result has a decimal part even when the total divides evenly.

Example 2: a harder declaration

The next declaration is more complex. Read it out loud before you run the code.

Python · editable
[0, 2]

Read the first declaration as "positions is a dictionary from str to a list of int." The keys are words, and each value is a list of positions where that word appears. Read words as "words is a list," with no hint, and i as the position from enumerate.

The method setdefault returns the list for a word and creates an empty list if there is none. Then append(i) puts the position in that list. After the loop, positions["cat"] is the list of positions for cat, which is [0, 2].

The declaration cat_positions: list[int] reads as "cat_positions is a list of int." It has the same type as the value stored under "cat", so the hint is correct.

The name positions is a good one here. It says what each list holds, and it avoids names such as data that say nothing. A reader who sees positions["cat"] knows the result without opening the loop.

Example 3: a nested list that changes meaning

The last example shows how the same line can say two different things, depending on the hint. It uses a list of lists and reads the type from the outside in:

Python · editable
rows: 2
columns: 3
total: 21

Read grid: list[list[int]] as "grid is a list of lists of int." The outer list holds rows, and each row is a list of int. So len(grid) counts rows, and len(grid[0]) counts the cells in the first row.

The loop for row in grid: reads as "for each row in grid." The inner loop for cell in row: reads as "for each cell in row." The hints tell you the type of each loop variable. A checker that reads the hint grid: list[int] instead would report the inner loop as wrong, because the items of that list are plain numbers and not lists. Reading the hint first tells you which loops are valid.

Common mistakes

Using a keyword as a name. Names such as class, for, return and None are reserved. The message is invalid syntax or cannot assign to None, and the caret points at the keyword. Rename the variable, for example class_size instead of class.

Starting a name with a digit. 2fast = 3 fails with invalid decimal literal. Use fast_count or second_run.

Putting a hyphen in a name. my-score = 4 gives cannot assign to expression here. Use snake_case, as in my_score.

Reusing a built-in name. The line print = 5 is accepted, but the next call print("x") fails with TypeError: 'int' object is not callable. The name print now belongs to your number. Choose a name such as label instead.

Expecting a hint to be checked. x: int = "text" runs without an error. Run a type checker such as mypy on the code, or write a test, when the types matter.

Sharing a list by accident. p = q = [] gives two names one list. Write two separate lines, p = [] and q = [], when each name needs its own list.

Expecting a hint to create a value. The line y: int on its own does not create y. Reading y after it gives NameError: name 'y' is not defined. The hint records the type, and an assignment is still needed to give the name a value.

Assigning to a constant. LIMIT = 5 followed by LIMIT = 6 runs without an error, because Python does not enforce the convention. The capital letters are the signal to the reader, and a checker that understands typing.Final is the way to enforce it.

Practice

Work each exercise yourself before you open the answer.

  1. Read these hints aloud, one at a time, using the pattern "name is a ...". Write the reading for each.
counts: list[int]
prices: dict[str, float]
rows: list[list[int]]
board: list[list[str]]

Each exercise has an answer below a "Show the answer" line. Try each one first, then read the answer.

  1. Read each hint from the name outward.

Show the answer

  • counts is a list of int.
  • prices is a dictionary from str to float.
  • rows is a list of lists of int.
  • board is a list of lists of str. It is a grid of text items, one per cell.
  1. Find the error in each line and say what Python will complain about. Run each line in its own file, because Python stops at the first syntax error in a file.
2nd_place = 2
total-score = 10
class = 5
LIMIT = 5
print(LIMIT)

Show the answer

  • 2nd_place = 2 fails with invalid decimal literal, because a name cannot start with a digit. Rename it to second_place.
  • total-score = 10 fails with cannot assign to expression here, because a hyphen is not allowed in a name. Rename it to total_score.
  • class = 5 fails with invalid syntax, because class is a keyword. Rename it to class_size.
  • The line LIMIT = 5 and print(LIMIT) run and print 5. The capital letters say the value is fixed, and nothing stops a later assignment.
  1. Rewrite this program with better names and the same output. The output is 10 20 30 on one line, then 60. Keep the same logic.
x = [10, 20, 30]
y = 0
for i in range(len(x)):
    y += x[i]
print(x[0], x[1], x[2])
print(y)

Show the answer

values = [10, 20, 30]
total = 0
for i in range(len(values)):
    total += values[i]
print(values[0], values[1], values[2])
print(total)

The names values and total say what the variables hold. The loop counter i stays, because its meaning is clear in a short loop. A reader who wants the sum can also write sum(values).

  1. Read this hint and say what scores["ana"][0] returns, given the code that runs before it:
scores: dict[str, list[int]] = {}
scores["ana"] = [88, 92]

Show the answer

scores is a dictionary from str to a list of int. The expression scores["ana"] returns the list [88, 92], and [0] on that list returns the first item, 88. The result is an int, which the code can use in arithmetic.

  1. Explain why x, y = 1, [4, 5] gives y a list and x a plain number, and why p = q = [] gives both names one list.

Show the answer

In the first form, Python matches each name with one value on the right, so x is 1 and y is the list [4, 5]. In the second form, the assignment gives the same new list to both names, because p = q = [] evaluates [] once and stores the result in both names. A change through one name is then visible through the other. To give each name its own list, write p = [] and q = [] on separate lines.

What you now know

  • A name may use letters, digits and _, must not start with a digit, and must not be a keyword. The interpreter checks these rules.
  • Use snake_case for variables and functions, PascalCase for classes, and UPPER_SNAKE_CASE for constants.
  • Read a type hint by saying the name, then "is a", then the type from left to right. Square brackets hold the item types of a list or a dictionary.
  • A type hint is not checked when the program runs. A checker such as mypy reads the hints.
  • Two names on one line with = in a chain can share one object, so write separate lines when each name needs its own list.
  • Read the error messages for names and syntax closely. The last line names the problem, and the caret shows the place.
Check yourself: Arguments and naming (6 questions) →Check yourself: Cumulative check (6 questions) →Practice this topic: coding problems for Variables and Data Types →

Stuck? Ask the tutor

Ask about anything in this lesson. Answers are short and use simple words.

Sign in to ask the tutor.

← Command-line arguments in Python