Skip to main content

Day 24 - Starting Python

Skills: 8

Pre-reading: 9.1.1, 9.1.2, 9.1.3, 9.1.4, 9.1.5

Supplementary Videos

NOTE: import pytest is not needed anymore!

Functions and Conditionals in Python

Python in vscode.dev (10 mins)

Today we begin our transition to Python. We will still use vscode.dev: you do not need to install Python on your computer. Instead, we use an extension called Python Language Levels (PLL). It runs Python in the browser, the same way the Pyret extension runs Pyret.

Unlike Pyret, the output shows up in a separate interactions panel at the bottom of the screen, rather than being up at the side.

Install the extension

  1. Open your cs2000-scratch repository in vscode.dev the way you have all semester: on GitHub, view the repository and press . (or go to https://vscode.dev/github/YOURUSERNAME/cs2000-scratch).
  2. Click the Extensions icon in the left sidebar (the four squares). VSCode Extensions Icon
  3. Search for Python Language Levels.
  4. Click Install. If it asks you to trust the publisher, do that.
  5. If vscode.dev offers to install Microsoft's Python extension, don't. We are using Python Language Levels, not that one. If you already installed it, you can ignore the extra prompts, or disable it for this workspace.

Create a Python file and run it

  1. Make a new file named 24.py (same new-file icon you've used for .arr files).

  2. Put a small program in it, for example:

    print("hello python")
  3. Look at the top right of the editor, on the same bar as the file name. Click PLL: Run Python File.

    If you don't see it, open the Command Palette (Ctrl+Shift+P on Windows, Cmd+Shift+P on a Mac) and run PLL: Run Python File.

  4. The first run can take a little while: PLL is loading Python in the browser. Later runs should be faster.

  5. After it runs, look at the bottom of the window for a PLL panel (near Problems, Terminal, and so on). That panel is the interactions view: it shows print output, values, and errors. Your .py file stays open above it — PLL does not replace the editor the way Pyret does.

    If the panel isn't visible, use the Command Palette and run PLL: Show Interactions.

  6. The interactions view has a prompt at the bottom. After a file has been run, you can type extra expressions there (for example 1 + 1) and press Enter. Those run in the same session, so names you defined in the file are available.

Intro (20 mins)

Python and Pyret share many core ideas, but use different notation and conventions.

Example: defining a function

  • Pyret:
    fun gadget-cost(num-gadgets :: Number, label :: String) -> Number:
    doc: "computes cost, at $0.50 per gadget plus $0.05 per character in label"
    num-gadgets * (0.50 + (string-length(label) * 0.05))
    where:
    gadget-cost(1, "hi") is 0.60
    gadget-cost(10, "tech") is 7.00
    end
  • Python:
    def gadget_cost(num_gadgets: int, label: str) -> float:
    """computes cost, at $0.50 per gadget plus $0.05 per character in label"""
    return num_gadgets * (0.50 + (len(label) * 0.05))

Two differences to notice right away:

  • Python uses def, a colon, and indentation instead of fun / end.
  • A Python function only gives a result if you return it. If you forget return, the function produces None.

Example: conditionals

  • Pyret:
    fun add-shipping(order-amt :: Number) -> Number:
    doc: "adds 4 for orders <= 10 (but non-zero), 8 for orders < 30, 12 for larger orders"
    if order-amt == 0:
    0
    else if order-amt <= 10:
    order-amt + 4
    else if order-amt < 30:
    order-amt + 8
    else:
    order-amt + 12
    end
    end
  • Python:
    def add_shipping(order_amt: float) -> float:
    """adds 4 for orders <= 10 (but non-zero), 8 for orders < 30, 12 for larger orders"""
    if order_amt == 0:
    return 0
    elif order_amt <= 10:
    return order_amt + 4
    elif order_amt < 30:
    return order_amt + 8
    else:
    return order_amt + 12

Python writes elif where Pyret writes else if, and each branch that should produce a result needs its own return.

Testing

In Pyret, examples live in a where: block on the function. In Python, you write tests in separate functions whose names begin with test_. Different Python environments have you place them in different places, but for us, with PLL, you place these functions in the same file as the code they check. Tests use assert to check a boolean expression.

Put this in 24.py along with gadget_cost:

import pytest

def test_gadget_cost_small() -> None:
assert gadget_cost(1, "hi") == pytest.approx(0.60)

def test_gadget_cost_medium() -> None:
assert gadget_cost(10, "tech") == pytest.approx(6.50)
# Intentionally wrong, so you can see a failing test

Then click PLL: Run Python File. PLL runs the tests first and shows a pass/fail card in the interactions view. Failed assertions are clickable and jump to the test. After the tests, it still runs the rest of the file, so names like gadget_cost are available at the interactions prompt.

Fix the expected value in test_gadget_cost_medium to 7.00 and run again so both tests pass.

Conventions:

  • Test functions must be named starting with test_.
  • Put tests in the same file as the functions they test. You do not need a separate test_*.py file, and you do not run pytest in a terminal.
  • Use assert to check expected results. If the expression is False, the test fails.
  • Prefer a single assert per test function. A test stops at the first failing assertion.
  • For floating-point (decimal) results, use pytest.approx, which is analogous to Pyret's is-roughly. That requires import pytest at the top of the file.

Class Exercises (20 mins)

  1. Function syntax. Rewrite the following Pyret function in Python. Follow the design recipe, including a docstring and tests in the same file.

    fun greet(name :: String) -> String:
    doc: "produces Hello, name!"
    "Hello, " + name + "!"
    where:
    greet("Alice") is "Hello, Alice!"
    greet("Bob") is "Hello, Bob!"
    end

    Then design a Python function that takes a name and an age and returns a string like "Alice is 20 years old."

  2. Conditionals. Translate this Pyret function to Python, with tests:

    fun shipping-cost(weight :: Number) -> Number:
    doc: "if weight <= 1, cost is $5; if weight <= 5, cost is $10; otherwise, $20"
    if weight <= 1:
    5
    else if weight <= 5:
    10
    else:
    20
    end
    end
  3. Return. What happens if you forget the return statement in a Python function? Try it, write a test that shows the problem, and observe the result.