Lecture 1, part B — the tools, and the Python this course needs.
Programming Languages Lab · University of Pisa
Vincenzo Ciancia, CNR-ISTI
vincenzo.ciancia@isti.cnr.it · github.com/vincenzoml/PLDW
This course is about languages. To talk about them, we need one we can all run.
Install it, open an editor, see the parts of Python the course uses. Then you practise.
Before any code
PYTHON 3.12 OR LATER
The language. Older versions lack features we use.
VS CODE
The editor: free, on every system, with a debugger built in.
GIT
To download the course repository.
Check what you have, in a terminal:
python3 --version # Windows: py --version
git --version
winget install Python.Python.3.12
py --version
Either the installer from python.org/downloads, or winget in a terminal.
With the installer, tick “Add python.exe to PATH” on the first screen.
On Windows the command is py (or python), not python3. If typing python opens the Microsoft Store, Python is not installed yet.
brew install python
python3 --version
Either the installer from python.org/downloads, or Homebrew (brew.sh) in a terminal.
Careful: macOS may already have a python3, installed with Apple's developer tools. It is 3.9: too old.
# Ubuntu, Debian
sudo apt install python3 python3-venv python3-pip
# Fedora
sudo dnf install python3
# Arch
sudo pacman -S python
Python is almost always there already. Use the package manager of your distribution.
Ubuntu 24.04 and later ships 3.12. On an older system, use uv: next slide.
uv python install 3.12
uv venv
uv pip install -r requirements.txt
uv run hello.py
uv installs Python, makes the virtual environment and installs packages, with the same commands on Windows, macOS and Linux.
Install it once, from docs.astral.sh/uv. It does not touch the Python of your system.
An alternative to the last three slides, not an extra step. VS Code finds the .venv it creates.
01 · INSTALL
From code.visualstudio.com, for your system.
02 · EXTENSION
Extensions panel → Python, by Microsoft. It brings type checking as you type and the debugger.
03 · COMMAND PALETTE
Ctrl+Shift+P (Cmd+Shift+P on a Mac): every command, by name. The one key to remember.
Any editor is fine for the exam. In class we use this one, so that we all see the same thing.
git clone https://github.com/vincenzoml/PLDW
cd PLDW
code .
All the code of the course is there: one folder per lecture.
From VS Code: Command Palette → Git: Clone, then paste the address.
No git yet? On GitHub, Code → Download ZIP works too, but you will need git for the project.
python3 -m venv .venv
source .venv/bin/activate # macOS, Linux
.venv\Scripts\activate # Windows
pip install -r requirements.txt
A private copy of Python for one project, with its own packages.
In VS Code: Command Palette → Python: Create Environment does all three steps; Python: Select Interpreter picks .venv.
requirements.txt lists what the course installs, such as lark for parsing, from lecture 3 on.
RUN
The ▶ button at the top right: Run Python File. The output appears in the terminal below.
TERMINAL
Ctrl+` opens one inside VS Code, already in your folder: python3 hello.py.
DEBUG
Click left of a line number for a breakpoint, then F5. Step with F10, enter a call with F11, read every variable on the left.
The debugger shows the state of a running program. Much of this course is about what that state is.
print("Hello, world!")
a = "Hello"
b = "World"
c = a + " " + b # strings concatenate
len(c) # 11
c.split() # ['Hello', 'World']
n = 3 # an int
x = 4.5 # a float
ok = True # a bool
str(n) # '3', a string
int("42") # 42, a number
print(f"{c}: {n} times {x} is {n * x}")
A name refers to a value. = binds it.
An f-string puts values inside text: f"...{expression}...".
Names and what they refer to are the subject of a whole lecture: binding and scope.
a: int = 3
c: str = "Hello"
def add(x: int, y: int) -> int:
return x + y
print(add(2, 3)) # 5
print(add("2", "3")) # 23 -- Python does not stop you
Annotations say what a name should hold.
Python does not check them while running. VS Code underlines the second call in red; mypy file.py reports it from the terminal.
A type is a claim about a program that can be checked without running it.
my_list = [1, 2, "ciao", 4, 5]
my_list[0], my_list[-1], my_list[1:3] # 1, 5, [2, 'ciao']
my_list.append(6) # [1, 2, 'ciao', 4, 5, 6]
len(my_list) # 6
[0] + my_list # a new list: [0, 1, 2, ...]
first, rest = my_list[0], my_list[1:] # head and tail
A loop
squares = []
for x in [1, 2, 3, 4, 5]:
squares.append(x ** 2)
A comprehension
squares = [x ** 2 for x in [1, 2, 3, 4, 5]]
evens = [x for x in range(20) if x % 2 == 0]
The same meaning, two syntaxes: the second is closer to the mathematician's { x² | x ∈ S }.
def sign(n: int) -> str:
if n > 0:
return "positive"
elif n < 0:
return "negative"
else:
return "zero"
i = 0
while i < 3:
print(i)
i += 1
for word in ["a", "b"]:
print(word.upper())
Indentation decides what is inside the if and the loops: in Python, white space is syntax.
env = {"x": 4.0, "y": 2.0}
env["x"] # 4.0
env["z"] = 7.0 # add a name
"w" in env # False
env["w"] # KeyError: 'w'
env.get("w", 0) # 0: a default instead of an error
for name, value in env.items():
print(name, value)
A table from keys to values.
You have seen one already: an environment, from variable names to numbers. Every interpreter of the course keeps one.
def factorial(n: int) -> int:
if n == 0:
return 1
return n * factorial(n - 1)
def total(xs: list[int]) -> int:
if not xs:
return 0
return xs[0] + total(xs[1:])
factorial(5) # 120
total([1, 2, 3]) # 6
A function that calls itself on a smaller piece of the problem.
Every interpreter in this course is a recursive function: one call per piece of the program.
from collections.abc import Callable
import operator
def twice(f: Callable[[int], int], x: int) -> int:
return f(f(x))
twice(lambda n: n + 3, 10) # 16
def adder(k: int) -> Callable[[int], int]:
return lambda n: n + k # a function, returned
adder(3)(10) # 13
ops: dict[str, Callable[[int, int], int]] = {
"+": operator.add,
"*": operator.mul,
}
ops["*"](6, 7) # 42
A function can be passed, returned, and stored in a dictionary.
lambda writes a small one without a name. Callable[[int], int] is its type: takes an int, gives an int.
from dataclasses import dataclass
@dataclass(frozen=True)
class Num:
value: int
@dataclass(frozen=True)
class Add:
left: "Expr"
right: "Expr"
Expr = Num | Add
e = Add(Num(2), Add(Num(3), Num(4))) # 2 + (3 + 4)
@dataclass writes the boring parts of a class for you: constructor, printing, equality.
frozen=True: once built, it cannot change.
This is how the course writes the syntax of a language: one class per kind of node.
match: one case per kind of node
def evaluate(e: Expr) -> int:
match e:
case Num(v):
return v
case Add(l, r):
return evaluate(l) + evaluate(r)
evaluate(Add(Num(2), Add(Num(3), Num(4)))) # 9
match looks at the shape of a value and takes it apart.
case Add(l, r): if e is an Add, call its two children l and r.
Data classes plus match plus recursion: that is an interpreter. Lecture 2 is all about it.
with open("notes.txt", "w") as f:
f.write("first line\n")
f.write("second line\n")
with open("notes.txt") as f:
for number, line in enumerate(f, start=1):
print(number, line.strip())
open gives a file; with closes it when the block ends, even after an error.
Your programs will read other programs from files: this is how.
def safe_divide(a: float, b: float) -> float | None:
try:
return a / b
except ZeroDivisionError:
print("division by zero")
return None
safe_divide(1, 0) # prints, returns None
An error is an exception: it stops the program unless someone catches it.
Each has a name: opening a missing file raises FileNotFoundError, int("x") raises ValueError. Raise one yourself with raise ValueError("expected a number").
What 1 / 0 means is a decision. Python raises; you will decide for your own language.
# calc.py
from dataclasses import dataclass # standard library
from lark import Lark # installed with pip
def main() -> None:
print("calc is running")
if __name__ == "__main__":
main()
import takes code from elsewhere: Python's own library, a package installed with pip, or another file of yours.
The last two lines run main when the file is run, not when another file imports it.
In class, now
names = ["Ada", "Alan", "Grace"]
# expected output:
# Hello, Ada! (3 letters)
# Hello, Alan! (4 letters)
# Hello, Grace! (5 letters)
# [3, 4, 5]
# Grace
Level 1 of 4
for loop and an f-string print the three greetings.Run it three ways: with ▶, from the terminal, and with a breakpoint and F5.
# poem.txt
# the cat and the hat
# the end
# counts.txt, expected:
# the 3
# cat 1
# and 1
# hat 1
# end 1
Level 2 of 4
poem.txt line by line; line.split() gives the words.counts.get(w, 0) + 1.counts.txt, one word per line.poem.txt does not exist, catch the exception and print a message instead of crashing.
def fib(n: int) -> int: ...
def reverse(xs: list[int]) -> list[int]: ...
def compose(f: Callable[[int], int],
g: Callable[[int], int]) -> Callable[[int], int]: ...
fib(10) # 55
reverse([1, 2, 3]) # [3, 2, 1]
add1 = lambda n: n + 1
dbl = lambda n: n * 2
compose(add1, dbl)(5) # 11: first dbl, then add1
Level 3 of 4
fib and reverse without loops: recursion only.compose returns a new function: a lambda.mypy on your file: no errors.... marks the body you write.
def calc(text: str) -> int: ...
calc("2 + 5 - 3") # 4
calc("10 - 4 - 3") # 3: left to right
calc("7") # 7
calc("2 + + 3") # error: not well formed
calc("2 + x") # error
Level 4 of 4 · take home
+ and - only; no parentheses. Compute from left to right.text.split() gives the pieces.ValueError that says what is wrong.Then make it work without spaces too: "2+5-3".