Skip to content

I closed the window and my code was gone

  • Python · Foundations
  • 20 min read, about 1 h with practice
  • Concept
  • Asked often: low
  • Reviewed 2026-11

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.50

It 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?

  • 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.

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__":

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 too
Python 3.12.7

On 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 typeWhat happensWhen to use it
pythonA prompt, >>>, runs each line as you press EnterTrying one line, checking a value
python shop.pyPython reads the whole file, then runs it top to bottom, onceAnything 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:

Steppython shop.pyfrom shop import receipt
1Sets __name__ = "__main__"Sets __name__ = "shop"
2def receipt makes the functionThe same
3def main makes the functionThe same
4The if is true, so main() prints the lineThe if is false, so nothing prints

All three print the same line. What changes is whether you can keep the code, run it again, and use it from somewhere else.

Okay

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.

Better

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.py
qty = 3
price = 1.50
print(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.py
qty = 3
price = 1.50
print(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.

Best

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.py
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()
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.py
qty = 3
price = 1.50
print(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.

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

Loads about 6 MB the first time. Best on Wi-Fi.

  1. Not run yet: receipt(3, 'pens', 1.5) → '3 pens cost 4.50'
  2. Not run yet: receipt(1, 'book', 12) → '1 book cost 12.00'
  3. Not run yet: Extra test
  4. Not run yet: Extra test

Extra tests check tricky cases. You see their input only if they fail.

Try your own input
WayKeeps the codeRun againOthers can use itUse it when
Okay: the >>> promptNoType it all againNoTrying one line
Better: a scriptYespython shop.pyImporting runs itA short job you only run
Best: functions and a guardYespython shop.pyYes, quietlyAnything you will test or reuse
  • The wrong folder: python shop.py looks for the file in the folder the terminal is in. Move there with cd, or give the full path.
  • python not found: on Windows, run the installer again and tick “Add python.exe to PATH”, or use py. On macOS and Linux, try python3.
  • 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}" gives 0.30, though 3 * 0.1 is 0.30000000000000004. The :.2f rounds only what is shown.

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.argv is a list: the file name, then each word typed after it. For more options, use argparse.

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.

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

Loads about 6 MB the first time. Best on Wi-Fi.

  1. Not run yet: hours_and_minutes(135) → '2 h 15 min'
  2. Not run yet: hours_and_minutes(125) → '2 h 05 min'
  3. Not run yet: Extra test
  4. Not run yet: Extra test
  5. 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
Okay

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.

Better

// 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.

Best

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.

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

Loads about 6 MB the first time. Best on Wi-Fi.

  1. Not run yet: split_bill(1000, 3) → [334, 333, 333]
  2. Not run yet: split_bill(900, 3) → [300, 300, 300]
  3. Not run yet: split_bill(1003, 4) → [251, 251, 251, 250]
  4. Not run yet: Extra test
  5. 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
Okay

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.

Better

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 shares

Where it breaks: 1003 between 4 gives [253, 250, 250, 250]. The first person pays three cents more than the others.

Best

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.

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

Loads about 6 MB the first time. Best on Wi-Fi.

  1. Not run yet: main(['shop.py', '3', '1.50']) → '3 items cost 4.50'
  2. Not run yet: main(['shop.py']) → 'Usage: python shop.py QTY PRICE'
  3. Not run yet: main(['shop.py', 'three', '1.50']) → "Not a number: 'three' or '1.50'\nUsage: python shop.py QTY PRICE"
  4. Not run yet: Extra test
  5. 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
Okay

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.

Better

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.

Best

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.

A few quick questions. Ones you miss are remembered, so you can find your weak spots later.

  1. Multiple choiceYou typed ten lines at the >>> prompt, then closed the window. What happened to them?
  2. Predict the outputshop.py holds this code. Another file runs "import shop". What prints?
    def main():
        print("open")
    
    print("loaded")
    if __name__ == "__main__":
        main()
  3. Spot the bugA test does: assert total(2, 3) == 5. It fails, though the screen shows 5. Why?
    def total(a, b):
        print(a + b)
  4. Multiple choiceAt the >>> prompt you type python shop.py and get a SyntaxError. What should you do?

Add these to your review deck. Each card comes back a little later every time you get it right.

  • python --version shows which Python runs. On Windows, py works too; on macOS and Linux, try python3.
  • >>> is for trying a line. Anything you want to keep goes in a .py file.
  • python shop.py runs 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__":.

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