Python Foundations: Intermediate
if runs its block only when the expression after it is True. Here price > balance is True, so both indented prints run, and then line 6 runs because it was never inside the block. Swap the numbers so the condition is False and the indented lines are skipped, while line 6 still runs. Now watch what one change in spacing does to the same 6 lines.
What does this program print?
The 3 headers you will meet most
- if price > 100: runs its block once, and only when the expression is True
- for i in range(5): runs its block once per value, with i holding the current one each pass
- def total(price): names its block and runs none of it now; the block runs each time total(...) is called later
Each header gets a lesson of its own later. What matters here is the shape they share: a colon, then an indented block, then a return to the left margin. Press play on the loop below and watch the single indented line run 5 times while the header hands i a new value on each pass.
1for i in range(5):2 print(i)
A def block differs in timing. When the file runs past line 1, Python stores the 2 indented lines under the name calculate_total and runs none of them. They run on line 5, when the name is called with 100 and 0.08, and the result 108.0 travels back to be stored in result and printed.
1def calculate_total(price, tax_rate):2 total = price * (1 + tax_rate)3 return total45result = calculate_total(100, 0.08)6print(result)
Blocks inside blocks
A block can hold another header, which opens a deeper block one level further in. Each level is, by convention, 4 more spaces; Python requires only that every line of one block is indented the same amount. In the program below, ask of every line which header owns it. Lines 4 and 5 sit 8 spaces in, so they belong to the if, which itself belongs to the for. Line 6 sits 4 spaces in, belongs to the for alone and runs on every pass. Line 7 is back at the margin and runs once, after the loop has finished all 4 passes.
1count = 02for i in range(4):3 if i > 1:4 count = count + 15 print("counted", i)6 print("checked", i)7print("total", count)
Lines at 8 spaces run only when i > 1; the line at 4 spaces runs every pass; the margin line runs once.
Indentation errors that stop the program
| What you wrote | Error | Message |
|---|---|---|
| if 3 > 2: followed by print("yes") at the margin | IndentationError | expected an indented block |
| print("hi") pushed in 4 spaces after a plain line, with no header above it | IndentationError | unexpected indent |
| one line of a block indented with a tab, the other lines of that block with spaces | TabError | inconsistent use of tabs and spaces in indentation |
- Indent with 4 spaces per level, every time
- End every header line with a colon
- Set your editor to insert spaces when you press Tab
- Return to the margin to close a block
- Mix tabs and spaces in one file
- Indent a line that no header asked for
- Use 2 spaces in one block and 4 in another
- Expect a blank line to end a block; only dedenting does
Practice
Python ships with hundreds of modules, each a file of code written and tested for you: square roots, random numbers, dates, text in JSON form. Python loads a module only when you ask for it. The import statement finds the module's file, runs it once, and binds the module's name in your program so you can reach what it holds with a dot.
Read math.sqrt as the sqrt that lives inside math. After line 1 the name math refers to the module, and the dot looks inside it for a name. sqrt returns a float even for a perfect square, pi is a plain float with 15 digits after the point, and floor drops the fraction and hands back an int. Leave out line 1 and the name math means nothing to the program.
2 ways to import
import math brings in the whole module under one name, and every use carries the prefix. from math import sqrt, pi copies those 2 names into your program, so you call sqrt(81) with no prefix. The second form reads shorter, at the cost of hiding where sqrt came from when someone reads line 40 of your file.
- One name is added to your program: math
- Every use carries the prefix: math.sqrt(2)
- The origin of each name is visible at the call
- Safe when 2 modules both define a sqrt
- The name sqrt is added directly
- Calls read shorter: sqrt(2)
- A reader scrolls to the top to find the origin
- A later from cmath import sqrt silently replaces it
Which line fails when this program runs, and with what error?
A tour of the standard library
| Module | What it holds | One name inside |
|---|---|---|
| math | square roots, rounding, constants | math.sqrt(x) |
| random | random numbers and random choices | random.randint(1, 6) |
| datetime | dates, times and the gaps between them | datetime.date.today() |
| json | turning JSON text into Python values and back | json.loads(text) |
| os | the operating system: paths, folders, environment | os.getcwd() |
| time | pausing, and measuring elapsed seconds | time.sleep(2) |
Each of these is installed with Python itself, so import json works on any machine that has Python and nothing else. Naming a module that does not exist, or misspelling one, fails at the import line with a ModuleNotFoundError rather than somewhere later in the program.
Importing your own file
A module is any file ending in .py, including the ones you write. Save the constants and helpers that 2 scripts share in a file named pricing.py, and both scripts can import pricing. The module's name is the filename without .py, and Python looks for it first in the folder the running script lives in.
Importing pricing runs every line of pricing.py once, top to bottom, which is how TAX_RATE gets its value and with_tax gets defined. A print at the top level of pricing.py would run at import time too, which surprises people the first time a bare import produces output. Keep a module's top level to constants and definitions, and the importing script decides what gets printed.
Practice
Every program so far had its values written into the code. input() lets the person running the program supply a value while it runs. The call shows its prompt text, pauses, waits for the user to type and press Enter, and then hands back whatever was typed. The program resumes on the next line with that text stored wherever you assigned it.
The prompt is ordinary text, so end it with a space or the cursor sits jammed against the colon. Whatever comes back is a string, every time. Type 42 and input returns "42", 2 characters that happen to be digits. Press Enter with nothing typed and it returns "", the empty string. Spaces typed before or after the value survive as well.
| User types | Value stored | Type |
|---|---|---|
| Maya | "Maya" | str |
| 42 | "42" | str |
| 3.5 | "3.5" | str |
| (Enter, nothing typed) | "" | str |
| 7 | " 7 " | str |
Adding a number to typed text
Treating that string as a number is the most frequent mistake in a first program with input. The program below stands in for a session where the user typed 30 at an Age prompt; the variable holds exactly what input would have returned, so the result is the same as the live version.
Python refuses because + between a string and an int has no single meaning: join them, or add them? The message says which reading it took, concatenation, and what it could not accept, the int. Before any arithmetic, convert the text to a number with int() or float(). Each is a function that reads a string of digits and returns the matching numeric value.
int() accepts digits with an optional leading sign and ignores spaces around them, so int(" 7 ") returns 7. float() accepts those plus a decimal point, so float("3") returns 3.0. Text that isn't a number, such as letters or, for int(), a decimal point, makes the conversion fail with a ValueError, the signal that the type was right and the content was wrong.
The first 2 lines printed before line 3 failed, because a program runs every line up to the one that raises. int("3.5") fails the same way, since a decimal point is not a digit; convert through float() when the user may type a fraction.
The user types 7 at the prompt. What does this program print?
Convert at the moment of reading
The usual shape wraps the input call directly in the conversion, so the variable never holds the raw string and no later line can forget to convert it.
| Price typed | Quantity typed | Result |
|---|---|---|
| 2.50 | 4 | Total: 10.0 |
| 19.99 | 2 | Total: 39.98 |
| 5 | 2 | Total: 10.0 |
| abc | (never asked) | ValueError raised at line 1 |
| 2.50 | 1.5 | ValueError raised at line 2 |
The fourth row shows the program dying at line 1, so the Quantity prompt never appears. The last row shows that int() rejects a fraction even though a human would read 1.5 as a number. Checking the text before converting, and asking again when it fails, uses tools from a later lesson; for now the rule is that the conversion line is where bad input surfaces.
- ▸It always returns a str, for digits and for an empty line alike
- ▸It drops the Enter keypress; the newline is never part of the value
- ▸It keeps every other character typed, spaces included
- ▸It blocks: nothing after it runs until the user presses Enter
Practice
| Traceback line | What it tells you |
|---|---|
| Traceback (most recent call last): | A report follows; the frames are ordered so the one that failed comes last |
| File "main.py", line 3, in <module> | The file and the line number; <module> means top level, outside any def |
| print(totl) | The source text of that line, copied so the problem can be spotted without opening the file |
| NameError: name 'totl' is not defined | The error's name, a colon, then its message: what went wrong in Python's words |
The error name picks the category
| Error name | Python is saying | The usual cause |
|---|---|---|
| NameError | this name has never been assigned | a typo, a use above the assignment, or a missing import |
| TypeError | this operation does not work on these types | a str where a number was needed, often from input() |
| ValueError | the type is right and the content is wrong | int("ten") or int("3.5") |
| ZeroDivisionError | the right side of / was 0 | a count or a total that turned out empty |
| ModuleNotFoundError | no module with this name was found | a misspelled import, or a file in the wrong folder |
| IndentationError | the block structure cannot be parsed | a missing or an extra level of indentation |
More than one frame
When the failing line sits inside a def block, the traceback shows 2 frames: the call that started things, then the line inside the function that raised. Most recent call last means the frame at the bottom is the one that was running when the error happened.
| Traceback line | Frame |
|---|---|
| Traceback (most recent call last): | header |
| File "main.py", line 4, in <module> | frame 1: the call |
| print(average(10, 0)) | frame 1 source |
| File "main.py", line 2, in average | frame 2: inside average |
| return total / count | frame 2 source |
| ZeroDivisionError: division by zero | the error |
The bottom frame says line 2 divided by zero. The frame above explains why count was 0: line 4 passed it in. The fix belongs at line 4, or at whatever produced the 0, rather than at line 2, which did exactly what it was written to do. A traceback marks where the error surfaced; the cause can be any line that ran earlier.
What does the last line of this program's traceback say, and which line holds the mistake? Names assigned at the top of the file are visible inside the def.
Errors with no traceback header
Practice
Running python with a filename executes a script: Python reads the file, runs it top to bottom, and exits. Running python with no filename opens the interactive shell. It shows a >>> prompt, runs each line the moment you press Enter, and waits for the next one. Everything in this lesson works in both, with one difference that changes how each is used.
| You type at >>> | The shell shows |
|---|---|
| 2 + 3 | 5 |
| total = 10 * 4 | (nothing; an assignment has no value to show) |
| total | 40 |
| "ab" * 3 | 'ababab' |
| print(total) | 40 |
| import math | (nothing) |
| math.sqrt(2) | 1.4142135623730951 |
The shell prints the value of any expression you type, with no print call, and shows a string with quotes around it so that '40' and 40 can be told apart. A script does none of that. Put the same lines in a file and run it, and only the print line produces output.
Lines 1, 3 and 4 were computed and their results thrown away. In a script, output comes from print alone; in the shell, output also comes from the expression echo. A value you saw in the shell goes silent in a script until you wrap it in print.
You paste these 3 lines from a shell session into a file and run it as a script. What is printed?
Blocks in the shell
A header line in the shell switches the prompt to ..., meaning a block is open. Type the indented lines, then press Enter on an empty line to close the block and run it. That blank line is what the shell needs, unlike a script, where dedenting alone closes a block.
| Prompt | You type | Shell shows |
|---|---|---|
| >>> | for i in range(3): | |
| ... | print(i * 10) | |
| ... | (empty line) | 0, then 10, then 20 |
| >>> | i | 2 |
In the last row, i still holds its final value after the loop, because the shell remembers every name you assign until you close it. Close the shell with exit() or Ctrl-D and all of it is gone, which is why anything worth keeping goes in a file.
Which one, when
- Checking what an expression evaluates to
- Seeing what a function returns before relying on it
- Looking around a module: dir(math) lists every name in it
- Trying one line of a bigger script on its own
- Reproducing a traceback with the exact values that caused it
- Anything you will run twice
- Programs longer than a handful of lines
- Work that reads input() or prints for a user
- Code someone else will read or run
- Anything that must survive closing the terminal
The 2 work together. Try an expression in the shell until it gives the value you want, paste that line into the script, and run the script. When a traceback points at a line, type that line into the shell with the same values to watch the failure up close. dir(math) and help(math.floor) answer most questions about a module faster than a search does.
Capstone
The variable typed stands in for what input() returned. Where does this script fail, and what fixes it?
> An analyst at Dropbox writes a 30-line script that asks for a folder name and a minimum age in days, imports datetime, and prints every file in that folder older than the minimum, then a summary count. The first run prints a traceback ending in TypeError, because the minimum age typed at the second prompt was compared to an int while still a str.
Blocks, imports, input, tracebacks and the shell: the 5 habits that turn lines of Python into a program you can run.
- Category
- Python
- Difficulty
- intermediate
- Duration
- 20 minutes
- Challenges
- 18 hands-on challenges
Topics covered: Indentation and Code Blocks, Importing Modules, Reading Input From the User, Reading a Traceback, Trying Code in the Interactive Shell
Lesson Sections
- Indentation and Code Blocks (concepts: Conditionals)
In most programs, some lines run only when a condition holds, some run again and again, and some wait until they are called by name. Python decides which lines belong to such a group by looking at how far each line is indented. A header line ends with a colon, and every line indented beneath it is part of the header's block. The block ends at the first line that steps back to the header's own indentation. Line 5 lost its 4 spaces, so it left the block. It now runs on every checkout and reports a
- Importing Modules (concepts: Iteration)
The program stops on its first line with a NameError, the same error a misspelled variable produces. The module exists on disk, but nothing in this program has named it yet. Section 3 reads traceback lines in full; for now, a NameError on a module's name means a missing import. 2 ways to import A tour of the standard library Importing your own file Practice
- Reading Input From the User (concepts: Functions)
Adding a number to typed text Convert at the moment of reading Practice
- Reading a Traceback (concepts: Functions)
When a line raises an error that nothing handles, Python stops and prints a traceback: a report of where the program was when it failed. A new programmer sees a wall of text. The wall has 4 parts, and only 2 of them matter on a first read: the last line and the line above it. Read it bottom up. The last line names the kind of failure and says which name was missing. The line above shows the code, where the misspelling is visible. Only then is the line number needed, to go and fix it. The first l
- Trying Code in the Interactive Shell (concepts: Scope)
Blocks in the shell Which one, when Capstone The capstone pulls the 5 sections into one short script: an import, an indented block, a value standing in for user input, and a bug that produces a traceback. Predict the traceback first, then decide the fix. A traceback reports only the first failure, because the program stops there. Fix that line and rerun, and the program gets further, which can expose a second bug of another kind, such as a print whose indentation keeps it from ever running.