Python
HTML describes content, and CSS describes its appearance. HTML is a markup language and CSS is a style-sheet language: each describes part of a result for the browser to render. Python is a procedural language: you give the computer a sequence of steps to run. The rest of the course uses Python to automate tasks.
For most people, Python is harder than HTML and CSS. If you have never programmed before, expect the next couple of weeks to be challenging.
Run code often and compare its behavior with what you expected.
Install Python
Course Setup installed VS Code and Git but not Python, so install it now. Open 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; the two are identical. 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-pipQuit and reopen VS Code, choose Terminal → New Terminal, and check:
python3 --versionIt should print a version number rather than an error. Commands in this course use python3. On Windows, if a later instruction’s pip3 is not found, use python3 -m pip instead.
The REPL
The fastest way to experiment with Python is the REPL, the interactive prompt you get by running python3 in a terminal. (VS Code has a built-in terminal under Terminal → New Terminal.) 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 example.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). 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. Strings support operations that numbers do not:
>>> "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.
Names and Assignment
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. The line is an instruction to update a name, not an equation claiming that ten equals thirteen. 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, Indexes, and Truth Values
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 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. 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.
From Expressions to Functions
The REPL is for experiments; to keep work, write it 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. 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 example.py, then add a call below it, without indentation:
print(is_even(10))Run the saved file from the shell:
$ python3 example.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 example.py; Python runs the file and then keeps its definitions available in the REPL.
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.
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. 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)
3A 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. 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
Checking factorial by hand after every change is repetitive. A doctest automates that check and is the basis of every lab in this course.
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, the function’s documentation. Replace example.py with this definition, leaving out the standalone print call from the earlier experiment. Each >>> call and its expected return value form a test that Python can check automatically. Run the file through the doctest module from your terminal:
$ python3 -m doctest example.pyWhen every doctest passes, the command prints nothing and returns you to the prompt. Add -v to print each test and a tally:
$ python3 -m doctest -v example.py
...
3 passed and 0 failed.
Test passed.Suppose we wrote the incorrect expression return n % 2 == 1. The tests report:
$ python3 -m doctest example.py
**********************************************************************
File "example.py", line 5, in example.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. The same public doctests run automatically there and set a status badge to red or green. That badge is preliminary feedback, not the grade: submit the repository and branch through Gradescope, where instructor-owned tests check the required behavior. You may keep fixing, pushing, and resubmitting until all required tests pass.
This Week’s Lab
Complete the roughly thirty short functions in Doctests yourself even though an LLM could write them in seconds; later problems assume that you have practiced the underlying steps.