Serpens

From the top of a file to the name that still points at the object.

Thirteen short programs on this page. Each one has a check you answer here. Try it opens Python Tutor with that program, and only when you ask. Modules, threads, and async are on the next page.

A small program

The file runs from the top

A Python file is executed from the first line to the last. def main stores a function. The function runs when the last lines call it, which they do when you run this file as a program.

def main() -> None:
    print("The python holds still.")

if __name__ == "__main__":
    main()

What to notice. __name__ is "__main__" when this file is the program you started. The same file can later be imported, and then those last lines skip the call. The programs on this page are Python 3.

Where does this program start?
What line does the program write?
Try it

An f-string fills a placeholder

print writes a line. An f before the opening quote makes braces into placeholders. The name inside the braces is filled in when the line runs.

coils = 12
print(f"Coils: {coils}")

What to notice. The braces are not printed. {coils} is replaced by the value 12. Without the f, the braces would be ordinary characters.

What is printed?
What does the f before the quote do?
Try it

A project is a directory

A small program can be one file. A project is a directory around it. pyproject.toml names the project. hello.py holds the program you run.

# serpens-field/
# ├── pyproject.toml
# └── hello.py

print("python hello.py")

What to notice. pyproject.toml is the manifest. hello.py is the script. Comments with # are not executed.

This site does not run Python. Try it opens the program that would live in hello.py. On your own machine, python hello.py runs that file.

Which file holds the program you run?
On your own machine, which command runs this file?
Try it

Syntax

A name can be rebound

A name refers to an object. Writing the name again points it at a new object. There is no separate mark that allows the change. The old object stays as it was.

bolts = 4
coats = 1
coats += 1
print(f"{bolts} bolts, {coats} coats")

What to notice. coats += 1 is coats = coats + 1. The name coats moves from 1 to 2. bolts still refers to 4.

What is printed?
What happens if you add the line bolts = 5 before the print?
Try it

A few types, and a hint

int is a whole number. bool is True or False. str is text, written with quotes. A colon and a type after a name is an annotation. The program runs without checking it.

piers: int = 2
awake: bool = True
mark: str = "S"
name: str = "Serpens"
print(piers, awake, mark, name)

What to notice. The four values are an int, a bool, and two strings. Python has no separate one-character type. The annotations are for a reader, and for a type checker you run on purpose.

What is printed?
What happens if the next line is piers = "two"?
Try it

return hands a value back

def names a function. The parameters sit in the parentheses. The arrow and a type are an annotation for the returned value. return is what actually hands that value back. A function with no return hands back None.

def span(piers: int) -> int:
    return piers * 6

print(span(2))

What to notice. return piers * 6 leaves the function with 12. The annotation -> int describes that number. It is not the return itself.

What is printed?
What does span return if you delete return and leave the line piers * 6?
Try it

if and for

if runs an indented block when the condition is true. for walks a sequence. range(1, 3) yields 1 and then 2. The end value is not included.

length = 3
if length > 2:
    print("coil")
for segment in range(1, 3):
    print(f"segment {segment}")

What to notice. The block is the indent, not braces. The condition has no parentheses. range(1, 3) stops before 3. range(1, 3) is two steps.

What is the first line printed?
How many lines start with segment?
Try it

Data

A class bundles data and methods

class starts a new type. __init__ runs when you call the type, and self is the new object. A method's first parameter is that object. snake.label() passes snake in as self.

class Snake:
    def __init__(self, meters: int) -> None:
        self.meters = meters

    def label(self) -> str:
        return f"{self.meters}m"

snake = Snake(12)
print(snake.label())

What to notice. self.meters stores 12 on the object. label reads that attribute and builds the text 12m.

What is printed?
What is self inside label?
Try it

match picks a case

match compares a value with a series of cases. The first case that fits runs. A value that fits none of them falls through. The language does not require a case for every possible string.

def report(coat: str) -> str:
    match coat:
        case "sound":
            return "sound"
        case "shed":
            return "shed"
    return "other"

print(report("shed"))

What to notice. case "shed" matches the argument, so report returns shed. return "other" runs only when no case matched.

What is printed?
What happens if the sound case is deleted and report is called with sound?
Try it

A list can grow. A str is replaced.

A list is an ordered collection. append adds an item to that same list. A str cannot grow in place. title += "s" points the name at a new string.

names = ["north", "south"]
names.append("center")
title = "span"
title += "s"
print(len(names), title)

What to notice. append changes the list object. += on a string rebinds the name. len counts the items in the list, which is now three.

What is printed?
Why does title += "s" create a new string?
Try it

The Python idea

Assignment adds a name

moved = name does not copy the characters and does not take the object away from name. Both names refer to the same object. is is true when two names refer to one object.

name = "Serpens"
moved = name
print(f"{moved} {name is moved}")

What to notice. After the assignment, name and moved are two ways to reach one string. The first name still works.

What is printed?
What does moved = name do?
Try it

A function can change the object you passed

The parameter scales is another name for the list in the caller. append changes that list. When the function returns, the caller sees the new item, because there was only one list.

def add_scale(scales: list[int]) -> None:
    scales.append(1)

scales = [1, 2]
add_scale(scales)
print(len(scales))

What to notice. The call does not copy the list. len is 3 because append changed the object the caller still names.

What is printed?
What happens if the function takes an int and does count += 1, and the caller keeps its own name for that int?
Try it

None and an exception

int(text) builds an integer from digits. A string that is not an integer raises ValueError. try runs the conversion. except runs when that error is raised, and this function returns None to say there was no number.

def meters_from(text: str) -> int | None:
    try:
        return int(text)
    except ValueError:
        return None

print(meters_from("12"))

What to notice. "12" converts to the integer 12, so the except block does not run. None is a real object. It is how this function reports that the text was not a number.

What does this program print?
What does meters_from return when the text is not an integer?
Try it

The next page

The short path ends at None and exceptions. Duck typing, modules, comprehensions, threads, and async each have their own program on the next page.

Continue