Python
You are organizing game night: keep a score, check whether the players can pair up, report the gap between two scores, and count the possible playing orders. Repeating those calculations by hand gets tedious whenever another person arrives or a score changes. Write a Python helper to produce the report. HTML and CSS describe webpages. Python lets us write instructions a computer can run.
If this is your first time programming, give yourself time to experiment. Run small pieces of code and check what they do before adding more.
Installing Python
Course Setup installed VS Code and Git but not Python, so install it now. Follow the instructions for your operating system.
macOS
Course Setup already installed Homebrew. In VS Code, choose Terminal → New Terminal and use it to install Python:
brew install pythonIf brew is not found, return to the macOS instructions in Course Setup and finish Homebrew’s Next steps.
Windows
Install the Python install manager from python.org/downloads or the Microsoft Store. Then open a new terminal in VS Code and run python3 once. The manager downloads the current Python the first time it is needed.
Linux
Most distributions ship Python. On Ubuntu or Debian, make sure the tools are present with:
sudo apt install python3 python3-pip python3-venvCheck Python
Quit and reopen VS Code, choose Terminal → New Terminal, and check:
python3 --versionIt should print a version number rather than an error. Later, after activating a virtual environment, we will use python and python -m pip to run code and install libraries with that environment’s interpreter.
The Python Prompt
Before saving a program, try a few calculations: add two scores, divide players into pairs, and inspect the kinds of results Python gives back. Run python3 in the terminal to open the REPL, Python’s interactive prompt. REPL stands for Read, Eval, Print, Loop: it reads a line, evaluates it, prints the result, and repeats. Its prompt is >>>. The prompts in the examples show where Python is waiting for input. Type only the code after them. Use exit() to leave Python and return to the shell before running a command such as python3 game_night.py. Type an expression and it shows you the value that expression computes:
>>> 17 + 25
42
>>> 2 ** 10
1024The ** is exponentiation, so 2 ** 10 is two to the tenth. Every value has a type. Python’s two division operators return different kinds of results: / gives a float, while // rounds the quotient down to a whole number:
>>> 7 / 2
3.5
>>> 7 // 2
3The 3.5 is a float (a number with a decimal point) and the 3 is an int (a whole number). Seven players can make three complete pairs, with one player left over. You can ask any value its type with the type function:
>>> type(4)
<class 'int'>
>>> type(4.0)
<class 'float'>
>>> type("hi")
<class 'str'>
>>> type(True)
<class 'bool'>Two other common types are str and bool. A str (string) is text in quotes. A bool (boolean) is one of the two values True or False. Player names and messages are strings. Try two string operations:
>>> "ha" * 3
'hahaha'
>>> len("automate")
8Multiplying a string repeats it, and len reports how many characters it holds. Practice in the REPL until you can predict each result.
Variables and Assignment
The score changes after each round, so we need to store its current value. To reuse a value, give it a name with =. An assignment evaluates the expression on the right and stores its result under the name on the left:
>>> score = 10
>>> score = score + 3
>>> score
13
>>> score == 10
FalseIn score = score + 3, Python reads the old value, adds three, and assigns the new value to score. One equals sign assigns. Two equals signs compare and return a boolean. An assignment does not display a result in the REPL, so enter the name by itself when you want to inspect its value.
Lists, Dictionaries, and Conditions
Store the colors of three playing pieces in order, so the program can select one by its position. A list stores an ordered sequence of values inside square brackets. Positions are numbered from zero, so index 0 selects the first item and len reports the number of items:
>>> colors = ['red', 'green', 'blue']
>>> colors[0]
'red'
>>> colors[2]
'blue'
>>> len(colors)
3A negative index counts backward from the end: colors[-1] is 'blue' and colors[-2] is 'green'. An index must identify an item that exists. A three-item list has indexes 0, 1, and 2.
A player has several pieces of information with different meanings. A dictionary stores values under names called keys, useful when a record has several fields:
>>> person = {'name': 'Ada', 'age': 20}
>>> person['name']
'Ada'
>>> person['age'] = 21
>>> person['age']
21colors[0] asks for a position in a sequence. person['name'] asks for a named field. The quotes around 'name' make it a string key rather than a variable to look up. Later files and web responses will often contain lists of dictionaries, one dictionary per record.
Python also treats values as true or false when they appear in a condition. Zero, an empty string, an empty list, and None are falsey. Nonzero numbers and nonempty strings and lists are truthy. None is Python’s value for an absent result, which we will see when a function has no value to return. For a list of scores, we might need to check whether the first score is nonzero or whether all three results have arrived. The operators and and or combine these conditions:
>>> items = [0, 2, 4]
>>> bool(items[0])
False
>>> bool(len(items) == 3)
True
>>> bool(items[0] or len(items) == 3)
TrueRead a or b as “true when at least one side is truthy” and a and b as “true only when both sides are truthy.” They stop as soon as the result is decided: or skips its right side when the left is truthy, and and skips it when the left is falsey. These operators return one of their operands. bool(...) converts that result to True or False.
The same list operations work on the people playing tonight. Keep this small roster for the helper:
>>> players = ['Ada', 'Teal', 'Sam', 'Jo']
>>> players[0]
'Ada'
>>> len(players)
4Functions
Save reusable code in a .py file. The unit of reusable work in Python is the function: a named block of code that takes inputs and produces an output. Every time the roster changes, we need to know whether its size is even so everyone can have a partner. We have already called functions (type(...) and len(...) are both functions), and now we write our own with def:
def is_even(n):
return n % 2 == 0The def line names the function is_even and says it takes one parameter, n, a stand-in for whatever value we call it with. The indented line is the body, the steps that run when the function is called. The % operator is the modulus: it gives the remainder after division, so n % 2 is 0 exactly when n is even. The expression n % 2 == 0 compares that remainder to zero with == and evaluates to a bool. return sends that bool back to the calling code.
Defining a function records its instructions. It does not run its body yet. Save the definition above in game_night.py. Keep this file open as we add the other calculations. Add a call below the definition, without indentation:
print(is_even(10))Run the saved file from the shell:
$ python3 game_night.py
TrueA script runs from top to bottom and does not automatically display expression values as the REPL does. That is why the script needs print around the function call. To experiment with the saved function at a Python prompt, run python3 -i game_night.py. Python’s -i option enters interactive mode after running the file, keeping its definitions available in the REPL. After each later edit to game_night.py, save the file, leave the old prompt with exit(), and run python3 -i game_night.py again before trying the new function. An open Python session does not automatically reload edits saved in VS Code.
return and print do different jobs. return sends a value back to the surrounding program, while print displays text and returns None. Other code cannot use the displayed result as a value. Compare the two:
>>> is_even(10)
True
>>> is_even(10) and is_even(7)
FalseThe second example combines two returned booleans with and. Swap the return for a print and the function returns None instead of the computed boolean. Since None is falsey, and then stops after the first call, even when the function printed True.
The new Python session does not retain variables from earlier sessions. Re-enter players = ['Ada', 'Teal', 'Sam', 'Jo'] at its prompt, then use is_even(len(players)) to check our roster. Four players can form two pairs. The function also works for ten or seven players, as in the experiments above, without changing its definition.
Conditionals
After a round, we want the distance between two scores regardless of which one is larger. Subtracting in one order gives a positive number. Reversing the order gives a negative one. An absolute-value function turns either difference into the same gap. Leave the interactive prompt with exit() before editing the file. Add the next definition below is_even in game_night.py. Functions use control flow to choose between paths or repeat a step. An if statement runs its block only when a condition is true, with an optional else for the other case:
def absolute_value(n):
if n < 0:
return -n
else:
return nIndentation groups the statements belonging to each block. Use four spaces for each level. Save the file and run python3 -i game_night.py again to load the new definition. Calling absolute_value(-3) takes the first branch and returns 3. Calling absolute_value(3) takes the second and also returns 3:
>>> absolute_value(-3)
3
>>> absolute_value(3)
3Loops
For four players, there are four choices for who goes first, then three for second, two for third, and one for last. That makes 4 * 3 * 2 * 1, or 24, possible orders. When the roster changes, we want the helper to repeat the multiplication for us. Exit the prompt, add factorial below the other definitions in game_night.py, and save. A while loop repeats its block as long as a condition holds, and a for loop repeats once per item in a sequence. The built-in range creates number sequences for loops, so range(1, n + 1) counts from 1 up to n:
def factorial(n):
result = 1
for i in range(1, n + 1):
result = result * i
return resultWe start an accumulator result at 1, then let i take each value from 1 to n, multiplying result by i and storing it back each time. When the loop finishes, result holds 1 * 2 * ... * n, and we return it. Run python3 -i game_night.py to try the new function. For factorial(4), the loop visits i = 1, 2, 3, 4, and result becomes 1, 2, 6, then 24:
>>> factorial(4)
24
>>> factorial(0)
1The endpoint of range is excluded, which is why n + 1 includes n. For zero, the range is empty and the initial 1 is returned without running the body. The return is outside the loop. Indenting it into the loop would stop the function after the first multiplication. What would go wrong if we started result at 0 instead of 1? Try it in the REPL.
Doctests
A later edit could break one of the three calculations we have checked. Checking factorial by hand after every change is repetitive. A doctest automates checks like these and provides the local feedback for this week’s lab. Start with tests for is_even, the pairing check.
A doctest is an example of your function’s behavior, written right into its documentation, in the exact form of a REPL session: the >>> prompt, the call, and the expected result on the next line:
def is_even(n):
'''
Return True if n is even and False if n is odd.
>>> is_even(0)
True
>>> is_even(7)
False
>>> is_even(-8)
True
'''
return n % 2 == 0The triple-quoted text under the def line is a docstring, short for documentation string. A doctest tests the examples in that documentation. Replace only the is_even definition in game_night.py with this version, keeping absolute_value and factorial below it. Remove the standalone print(is_even(10)) call from the earlier experiment. Exit the interactive prompt before running the terminal commands below. Each >>> call and its expected return value form a test that Python can check automatically. The -m option runs a module, a named piece of Python code. Here it runs the doctest module on our file:
$ python3 -m doctest game_night.pyWhen every doctest passes, the command prints nothing and returns you to the prompt. Add -v for verbose output, which prints each test and a tally:
$ python3 -m doctest -v game_night.py
...
3 passed and 0 failed.
Test passed.Suppose we wrote the incorrect expression return n % 2 == 1. The tests report:
$ python3 -m doctest game_night.py
**********************************************************************
File "game_night.py", line 5, in game_night.is_even
Failed example:
is_even(0)
Expected:
True
Got:
False
...
***Test Failed*** 3 failures.For every broken example it prints the exact call, the expected value (Expected: True), and the actual value (Got: False), followed by a failure count. All three examples fail here because == 1 asks the opposite question. In a lab, the tests are provided. Write each function until every Got matches every Expected and the command reports no failures.
The lab is a .py file full of functions with doctests and empty bodies. Fill in the bodies until the doctests pass on your machine, then push the file to GitHub. After the local doctests pass, submit the repository and branch through Gradescope for grading with instructor-owned tests. You may keep fixing, pushing, and resubmitting until all required tests pass.
Game-Night Report
The three functions we tried are already in game_night.py. is_even also has automated doctests. The completed script is available for reference. Add the roster, scores, and report below their definitions:
players = ['Ada', 'Teal', 'Sam', 'Jo']
scores = {'Ada': 10, 'Teal': 13}
print('players:', len(players))
print('everyone can pair up:', is_even(len(players)))
print('score gap:', absolute_value(scores['Ada'] - scores['Teal']))
print('possible playing orders:', factorial(len(players)))The list supplies the player count, the dictionary keeps each score attached to a name, and the functions answer the three questions. Run the report:
$ python3 game_night.py
players: 4
everyone can pair up: True
score gap: 3
possible playing orders: 24The report statements also run when doctest imports this finished file. Seeing those four report lines during a test run is expected. A failed doctest prints Expected and Got as before.
Add a fifth name to players and predict which lines will change. Changing the roster updates the pairing check and the number of playing orders. Later, we will read scores from a file so the program can work with results saved after game night.
List Operations
The organizer also needs a list of scores worth celebrating and a few recent results. Create rounds.py beside game_night.py for these experiments. The completed file is available for reference. A list can grow with its append method, which adds one value at the end. Build a new list from the scores that meet a condition:
scores = [12, 7, 15, 4, 18, 9]
celebrated = []
for score in scores:
if score > 10:
celebrated.append(score)
print(celebrated)The output is:
[12, 15, 18]
The accumulator is a list this time. Each matching score adds an item. append changes the list and returns None, so do not assign its return value back to celebrated. Append the next lines to rounds.py and rerun it after each block. A slice selects several items, starting at its first index and stopping before its second:
print(scores[1:4])
print(scores[:3])
print(scores[-3:])These added lines print:
[7, 15, 4]
[12, 7, 15]
[4, 18, 9]
An omitted endpoint means the beginning or end of the list. The first slice includes indexes 1, 2, and 3. Unlike one-item indexing, a slice can extend past the end. A short list supplies the items it has.
Arrival order and score order answer different questions. sorted returns a new list in ascending order, leaving the original alone:
ranked = sorted(scores)
print(ranked)
print(scores)These added lines print:
[4, 7, 9, 12, 15, 18]
[12, 7, 15, 4, 18, 9]
The list method scores.sort() would reorder scores itself and return None. Use the new sorted list when the original arrival order still matters. Combine ranking with a slice to choose the leaders in the lab.
Several rounds give us a list containing other lists. The outer loop selects a round. The inner loop visits that round’s scores:
rounds = [[12, 7], [15, 4], [18, 9]]
all_scores = []
for round_scores in rounds:
for score in round_scores:
all_scores.append(score)
print(all_scores)These added lines print:
[12, 7, 15, 4, 18, 9]
The inner loop finishes before the outer loop moves to the next round. For a position-based rule, use for i in range(len(scores)) and read scores[i]. This gives you both the index and the value.
While Loops
A while loop is useful when a condition determines when to stop. Append a countdown to rounds.py:
seconds = 3
while seconds > 0:
print(seconds)
seconds = seconds - 1
print('start the round')These added lines print:
3
2
1
start the round
The condition is checked before each repetition. After printing 1, the update makes seconds zero, so the loop ends. Leaving out the update would keep the condition true forever. Press Ctrl+C to stop a running program if you try that experiment. Restore the update before continuing. The lab’s digit-counting hint uses the same pattern with integer division to make the remaining number smaller each time.
This Week’s Lab
Complete the 33 short functions in Doctests yourself. The practice with conditions, loops, and lists will help you read and repair later programs.