Python, with VS Code

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

Setting up

What you need

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
  

Installing Python on Windows


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.

Installing Python on macOS


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.

Installing Python on Linux


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

Or: uv, one tool on every system


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.

VS Code

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.

The course repository


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.

A virtual environment


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, and look inside

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.

The Python of this course

Values, names, and strings


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.

Types you can write down


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.

Lists

10-521-4"ciao"2-343-254-1my_list[1:3]

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
  

Loops, and the shorter way

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

Deciding and repeating


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.

Dictionaries: names to values


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.

Recursion


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.

Functions are values


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.

Data classes: a tree in a few lines


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.

Reading and writing a file


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.

When things go wrong


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.

A program in a file


# 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

Your turn

Exercise 1 · Names, strings, lists


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

  1. A for loop and an f-string print the three greetings.
  2. A comprehension builds the list of lengths.
  3. A negative index gives the last name.

Run it three ways: with ▶, from the terminal, and with a breakpoint and F5.

Exercise 2 · Count the words


# 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

  1. Read poem.txt line by line; line.split() gives the words.
  2. Count them in a dictionary: counts.get(w, 0) + 1.
  3. Write counts.txt, one word per line.
  4. If poem.txt does not exist, catch the exception and print a message instead of crashing.

Exercise 3 · Recursion and functions


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

  1. fib and reverse without loops: recursion only.
  2. compose returns a new function: a lambda.
  3. Run mypy on your file: no errors.

... marks the body you write.

Exercise 4 · At home: a calculator


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

  1. Integers, + and - only; no parentheses. Compute from left to right.
  2. Start with spaces between numbers and operators: text.split() gives the pieces.
  3. If the text is not well formed, raise a ValueError that says what is wrong.

Then make it work without spaces too: "2+5-3".