I closed the window and my code was gone
See the problem
Section titled “See the problem”You help at a small shop. Each evening you work out what a customer owes. You open Python and type:
>>> qty = 3>>> price = 1.50>>> print(f"{qty} pens cost {qty * price:.2f}")3 pens cost 4.50It works. You close the window. The next evening you open Python again, and the prompt is empty. Your three lines are gone, so you type them all again.
How do you keep code, run it again with one command, and let other code use it?
Define the task
Section titled “Define the task”- Input: how many items, what they are, and the price of one.
- Output: a line like
3 pens cost 4.50, with two places after the point. - Rules: the code lives in a file called
shop.py. One command in the terminal runs it. Other code, like a test, can use the sum without the file printing anything. - The hard part: one file that does its job when you run it, and stays quiet when another file uses it.
Example: running python shop.py prints 3 pens cost 4.50. A test that does from shop import receipt gets the function, and nothing prints.
Pause and think
Section titled “Pause and think”Hints open one at a time:
Hint 1 of 3
Code typed at >>> lives only in memory. A file on disk stays after you close the window.
Hint 2 of 3
Python sets a name called __name__ in every file. Run the file, and it is "__main__". Import it, and it is the file’s name.
Hint 3 of 3
Put the work in a function, and call it only under if __name__ == "__main__":
Understand the idea
Section titled “Understand the idea”Install Python. Get it from python.org. On Windows, tick “Add python.exe to PATH” in the installer. Then open a terminal: Terminal on macOS, PowerShell on Windows. Check what you have:
python --version # Windows: py --version works tooPython 3.12.7On macOS and Linux the command may be python3. Any Python from 3.11 on works for this site.
Two ways to run code. Python has two modes:
| You type | What happens | When to use it |
|---|---|---|
python | A prompt, >>>, runs each line as you press Enter | Trying one line, checking a value |
python shop.py | Python reads the whole file, then runs it top to bottom, once | Anything you want to keep or run again |
Type exit() to leave the prompt. Everything you made there is lost.
Run or import. Python gives every file a name called __name__. When you run a file, Python sets its __name__ to "__main__". When another file imports it, __name__ is the file’s own name, like "shop". So this line asks, “Was I run?”:
if __name__ == "__main__": main()Here is what Python does with best.py from the ladder below, in both cases:
| Step | python shop.py | from shop import receipt |
|---|---|---|
| 1 | Sets __name__ = "__main__" | Sets __name__ = "shop" |
| 2 | def receipt makes the function | The same |
| 3 | def main makes the function | The same |
| 4 | The if is true, so main() prints the line | The if is false, so nothing prints |
Climb the ladder
Section titled “Climb the ladder”All three print the same line. What changes is whether you can keep the code, run it again, and use it from somewhere else.
Type it at the prompt
The idea: Start python and type each line at >>>.
On the shop: each line runs as soon as you press Enter. The answer shows at once, which is great for a quick check.
$ python>>> qty = 3>>> price = 1.50>>> print(f"{qty} pens cost {qty * price:.2f}")3 pens cost 4.50>>> exit()- Time
O(1)- Space
O(1)- Why
- Three quick sums.
Where it breaks: the code lives only while the prompt is open. Close it, and you type everything again tomorrow. Nothing else can use it either.
Save it in a file
The idea: Put the same lines in shop.py and run python shop.py.
The waste in Okay: you type the same lines every evening, when a file could keep them.
Save the file in a folder, open a terminal there, and run python shop.py. Python reads the file and runs it from the top. Lines starting with # are comments, which Python skips.
Highlighted lines are new in this rung.
# shop.py: run it with python shop.pyqty = 3price = 1.50print(f"{qty} pens cost {qty * price:.2f}")- Time
O(1)- Space
O(1)- Why
- The same sums, read from a file.
What changed from the previous rung
$ python>>> qty = 3>>> price = 1.50>>> print(f"{qty} pens cost {qty * price:.2f}")3 pens cost 4.50>>> exit()# shop.py: run it with python shop.pyqty = 3price = 1.50print(f"{qty} pens cost {qty * price:.2f}")Where it breaks: the numbers are fixed in the file, and the work isn’t in a function. A test that imports shop gets no receipt to call, and the import prints the line anyway.
A function and a main() guard
The idea: Put the sum in receipt(), the printing in main(), and call main() only when the file is run.
The waste in Better: the work is fixed to one customer, and importing the file runs it.
receipt() returns the line instead of printing it, so a test can check it. main() does the printing. And the if at the bottom calls main() only when you run the file.
Highlighted lines are new in this rung.
# shop.py: run it with python shop.pydef receipt(qty: int, name: str, price: float) -> str: return f"{qty} {name} cost {qty * price:.2f}"
def main() -> None: print(receipt(3, "pens", 1.50))
if __name__ == "__main__": main()- Time
O(1)- Space
O(1)- Why
- The same sums, inside functions.
What changed from the previous rung
# shop.py: run it with python shop.pyqty = 3price = 1.50print(f"{qty} pens cost {qty * price:.2f}")def receipt(qty: int, name: str, price: float) -> str: return f"{qty} {name} cost {qty * price:.2f}"
def main() -> None: print(receipt(3, "pens", 1.50))
if __name__ == "__main__": main()When Best isn’t best: for a one-off sum, the prompt is faster. And a three-line script you’ll never import doesn’t need the guard.
Write it yourself
Section titled “Write it yourself”Now write receipt in the page. Press Run tests to check your code. Python runs in your browser, so nothing is sent anywhere.
The tests call your function and check what it returns. A function that prints the line but returns nothing fails.
Write the receipt line
Ctrl + Enter runs- Not run yet:
receipt(3, 'pens', 1.5)→'3 pens cost 4.50' - Not run yet:
receipt(1, 'book', 12)→'1 book cost 12.00' - Not run yet: Extra test
- Not run yet: Extra test
Extra tests check tricky cases. You see their input only if they fail.
Try your own input
Compare
Section titled “Compare”| Way | Keeps the code | Run again | Others can use it | Use it when |
|---|---|---|---|---|
Okay: the >>> prompt | No | Type it all again | No | Trying one line |
| Better: a script | Yes | python shop.py | Importing runs it | A short job you only run |
| Best: functions and a guard | Yes | python shop.py | Yes, quietly | Anything you will test or reuse |
Common mistakes and edge cases
Section titled “Common mistakes and edge cases”- The wrong folder:
python shop.pylooks for the file in the folder the terminal is in. Move there withcd, or give the full path. pythonnot found: on Windows, run the installer again and tick “Add python.exe to PATH”, or usepy. On macOS and Linux, trypython3.- Indentation: Python uses the spaces at the start of a line to group code. Use four spaces, never a mix of tabs and spaces.
- Print or return: a function that prints shows the line, but gives back
None. Return the value, and print it where you need it. - Two places after the point:
f"{3 * 0.1:.2f}"gives0.30, though3 * 0.1is0.30000000000000004. The:.2frounds only what is shown.
Say it in the interview
Section titled “Say it in the interview”Questions to ask first
- Will this run as a script, be imported, or both?
- Which Python version does the team use?
A 60-second walkthrough
“I’d put the logic in a function that returns a value, so it’s easy to test. Printing goes in a
main()function. At the bottom,if __name__ == '__main__': main()runs it only when the file is run directly. When a test imports the module,__name__is the module’s name, so nothing runs on import.”
Likely follow-ups
- What is
__name__? A name Python sets in every module:"__main__"for the file you ran, the module’s name for an import. - Why not just print in the function? A function that returns can be tested and reused. Printing is a side effect for the caller to choose.
- How do you read arguments from the command line?
sys.argvis a list: the file name, then each word typed after it. For more options, useargparse.
Practice
Section titled “Practice”Three problems, from easy to hard. Each has hints, tests you can run here, and its own Okay, Better and Best answers. Try before you open them.
1. Hours and minutes (easy)
Section titled “1. Hours and minutes (easy)”Write hours_and_minutes(total). It takes a count of minutes and returns text like "2 h 15 min". Show the minutes with two digits, so 125 gives "2 h 05 min".
Hint 1 of 3
// divides and drops what is left over. % gives what is left over.
Hint 2 of 3
divmod(a, b) gives both at once, as a pair.
Hint 3 of 3
In an f-string, {m:02d} shows a whole number with at least two digits.
Hours and minutes
Ctrl + Enter runs- Not run yet:
hours_and_minutes(135)→'2 h 15 min' - Not run yet:
hours_and_minutes(125)→'2 h 05 min' - Not run yet: Extra test
- Not run yet: Extra test
- Not run yet: Extra test
Extra tests check tricky cases. You see their input only if they fail.
Try your own input
Show the Okay, Better and Best answers
Take away 60 at a time
The idea: While there are 60 or more minutes, take 60 away and add an hour.
It gets 135 right. But it shows 125 as 2 h 5 min.
def hours_and_minutes(total: int) -> str: hours = 0 while total >= 60: total = total - 60 hours = hours + 1 return f"{hours} h {total} min"- Time
O(h)- Space
O(1)- Why
- One trip round the loop for each of the h hours.
Where it breaks: the loop runs once per hour, so a huge number takes seconds. And the minutes aren’t padded to two digits.
// and %
The idea: total // 60 is the hours, and total % 60 is the minutes left.
The waste in Okay: it counts hours one by one, when one division gives them all.
Highlighted lines are new in this rung.
def hours_and_minutes(total: int) -> str: hours = total // 60 minutes = total % 60 return f"{hours} h {minutes} min"- Time
O(1)was O(h)- Space
O(1)- Why
- Two sums, whatever the size.
What changed from the previous rung
def hours_and_minutes(total: int) -> str: hours = 0 while total >= 60: total = total - 60 hours = hours + 1 return f"{hours} h {total} min" hours = total // 60 minutes = total % 60 return f"{hours} h {minutes} min"Where it breaks: 125 still shows as 2 h 5 min.
divmod and :02d
The idea: divmod gives the hours and minutes in one call, and :02d pads the minutes.
The waste in Better: it divides twice, and leaves the minutes unpadded.
Highlighted lines are new in this rung.
def hours_and_minutes(total: int) -> str: hours, minutes = divmod(total, 60) return f"{hours} h {minutes:02d} min"- Time
O(1)- Space
O(1)- Why
- One division.
What changed from the previous rung
def hours_and_minutes(total: int) -> str: hours = total // 60 minutes = total % 60 return f"{hours} h {minutes} min" hours, minutes = divmod(total, 60) return f"{hours} h {minutes:02d} min"When Best isn’t best: for dates and real clock times, use the datetime module. It knows about days, months and time zones.
2. Split the bill (medium)
Section titled “2. Split the bill (medium)”Write split_bill(cents, people). Split a bill, given in cents, between some people. Every cent must be paid. No two shares may differ by more than one cent, and bigger shares come first.
Example: split_bill(1000, 3) gives [334, 333, 333].
Hint 1 of 3
Work in whole cents. Decimals like 3.33 can’t add up to exactly 10.00.
Hint 2 of 3
cents // people is the share everyone gets. cents % people cents are left over.
Hint 3 of 3
Give one extra cent each to the first few people.
Split the bill
Ctrl + Enter runs- Not run yet:
split_bill(1000, 3)→[334, 333, 333] - Not run yet:
split_bill(900, 3)→[300, 300, 300] - Not run yet:
split_bill(1003, 4)→[251, 251, 251, 250] - Not run yet: Extra test
- Not run yet: Extra test
Extra tests check tricky cases. You see their input only if they fail.
Try your own input
Show the Okay, Better and Best answers
Divide and round
The idea: Divide the bill, round the share, and give it to everyone.
900 between 3 gives 300 each, which is right.
def split_bill(cents: int, people: int) -> list[int]: share = round(cents / people) return [share] * people- Time
O(p)- Space
O(p)- Why
- One share for each of the p people.
Where it breaks: 1000 between 3 gives three shares of 333. That adds up to 999, so a cent is never paid.
The rest goes to the first person
The idea: Everyone gets cents // people, and the first person pays what is left over.
The waste in Okay: rounding throws away the cents that don’t divide evenly.
1000 between 3 now gives [334, 333, 333], which adds up.
Highlighted lines are new in this rung.
def split_bill(cents: int, people: int) -> list[int]: share = cents // people shares = [share] * people shares[0] = shares[0] + cents - share * people return shares- Time
O(p)- Space
O(p)- Why
- The same list, with one share changed.
What changed from the previous rung
def split_bill(cents: int, people: int) -> list[int]: share = round(cents / people) return [share] * people share = cents // people shares = [share] * people shares[0] = shares[0] + cents - share * people return sharesWhere it breaks: 1003 between 4 gives [253, 250, 250, 250]. The first person pays three cents more than the others.
One extra cent each
The idea: divmod gives the share and the extra cents; the first extra people pay one cent more.
The waste in Better: it puts all the extra cents on one person.
[share + 1] * extra makes a list of the bigger shares, and + joins it to the rest.
Highlighted lines are new in this rung.
def split_bill(cents: int, people: int) -> list[int]: share, extra = divmod(cents, people) return [share + 1] * extra + [share] * (people - extra)- Time
O(p)- Space
O(p)- Why
- One share for each person.
What changed from the previous rung
def split_bill(cents: int, people: int) -> list[int]: share = cents // people shares = [share] * people shares[0] = shares[0] + cents - share * people return shares share, extra = divmod(cents, people) return [share + 1] * extra + [share] * (people - extra)When Best isn’t best: when shares should follow what each person ordered, split by weight instead. The decimal module helps with money that isn’t in cents.
3. Run it with numbers (hard)
Section titled “3. Run it with numbers (hard)”Make shop.py take the numbers from the command line: python shop.py 3 1.50. Write main(argv), where argv is the list sys.argv would hold: ["shop.py", "3", "1.50"]. Return "3 items cost 4.50".
If there aren’t exactly two numbers, return "Usage: python shop.py QTY PRICE". If one isn’t a number, return "Not a number: 'three' or '1.50'", a new line, then the usage line.
Hint 1 of 3
argv[0] is the file name. The numbers are argv[1] and argv[2], as text.
Hint 2 of 3
Check len(argv) before you look at argv[1], or a missing number crashes.
Hint 3 of 3
int("three") raises ValueError. Catch it with try and except ValueError. The exceptions lesson covers this.
Run it with numbers
Ctrl + Enter runs- Not run yet:
main(['shop.py', '3', '1.50'])→'3 items cost 4.50' - Not run yet:
main(['shop.py'])→'Usage: python shop.py QTY PRICE' - Not run yet:
main(['shop.py', 'three', '1.50'])→"Not a number: 'three' or '1.50'\nUsage: python shop.py QTY PRICE" - Not run yet: Extra test
- Not run yet: Extra test
Extra tests check tricky cases. You see their input only if they fail.
Try your own input
Show the Okay, Better and Best answers
Read argv[1] and argv[2]
The idea: Turn the two words into numbers and do the sum.
With two good numbers, it works.
def main(argv: list[str]) -> str: qty = int(argv[1]) price = float(argv[2]) return f"{qty} items cost {qty * price:.2f}"- Time
O(1)- Space
O(1)- Why
- Two conversions and one sum.
Where it breaks: python shop.py with no numbers crashes with IndexError, because argv[1] doesn’t exist.
Check the count first
The idea: Return the usage line unless argv has exactly three items.
The waste in Okay: it trusts the user to type two numbers.
Highlighted lines are new in this rung.
USAGE = "Usage: python shop.py QTY PRICE"
def main(argv: list[str]) -> str: if len(argv) != 3: return USAGE qty = int(argv[1]) price = float(argv[2]) return f"{qty} items cost {qty * price:.2f}"- Time
O(1)- Space
O(1)- Why
- One check more.
What changed from the previous rung
USAGE = "Usage: python shop.py QTY PRICE"
def main(argv: list[str]) -> str: if len(argv) != 3: return USAGE qty = int(argv[1]) price = float(argv[2]) return f"{qty} items cost {qty * price:.2f}"Where it breaks: python shop.py three 1.50 still crashes, now with ValueError.
Catch bad numbers, and guard main
The idea: Catch ValueError and say which words were wrong; call main only when the file is run.
The waste in Better: it checks the count of words, but not what they say.
The guard at the bottom passes the real sys.argv, so python shop.py 3 1.50 prints 3 items cost 4.50.
Highlighted lines are new in this rung.
import sys
USAGE = "Usage: python shop.py QTY PRICE"
def main(argv: list[str]) -> str: if len(argv) != 3: return USAGE try: qty = int(argv[1]) price = float(argv[2]) except ValueError: return f"Not a number: {argv[1]!r} or {argv[2]!r}\n{USAGE}" return f"{qty} items cost {qty * price:.2f}"
if __name__ == "__main__": print(main(sys.argv))- Time
O(1)- Space
O(1)- Why
- The same work, with a check.
What changed from the previous rung
import sys
USAGE = "Usage: python shop.py QTY PRICE"
def main(argv: list[str]) -> str: if len(argv) != 3: return USAGE qty = int(argv[1]) price = float(argv[2]) try: qty = int(argv[1]) price = float(argv[2]) except ValueError: return f"Not a number: {argv[1]!r} or {argv[2]!r}\n{USAGE}" return f"{qty} items cost {qty * price:.2f}"
if __name__ == "__main__": print(main(sys.argv))When Best isn’t best: with more than two or three options, use argparse. It writes the usage line and the error messages for you.
Check yourself
Section titled “Check yourself”A few quick questions. Ones you miss are remembered, so you can find your weak spots later.
Flashcards
Section titled “Flashcards”Add these to your review deck. Each card comes back a little later every time you get it right.
Cheat sheet
Section titled “Cheat sheet”python --versionshows which Python runs. On Windows,pyworks too; on macOS and Linux, trypython3.>>>is for trying a line. Anything you want to keep goes in a.pyfile.python shop.pyruns the file from the top, once, in the terminal’s folder.- Put the work in functions that return values, and start it under
if __name__ == "__main__":.
Go deeper
Section titled “Go deeper”- Using Python: installing and running Python on Windows, macOS and Linux.
- Using the Python interpreter, in the Python tutorial.
__main__: whatif __name__ == "__main__"means.- Next on the Python roadmap: Variables, print() and input().
Can you do these now?
If you’re not sure of any of these, scroll back to that part before moving on.
Status: Not started