Day 25 - Pyret to Python
Skills: 8
Pre-reading: 9.1.1, 9.1.2, 9.1.3, 9.1.4, 9.1.5, 9.1.6, 9.1.7
Supplementary Videos
Functions and Conditionals in Python, Lists and Dataclasses in Python
Intro (20 mins)
- Python and Pyret share many core ideas, but use different notation and conventions -- more significantly, the Pyret programming environment is designed to be easy to use, whereas there is more involved setup for Python (as we saw yesterday).
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.55
gadget_cost(10, "tech") is 6.00
end - Python:
gadget.py
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))
# Testing -- we'll come back to this
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:
gadget.py
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
# Testing -- we'll come back to this
Testing
-
In Pyret, you use
where:blocks for examples/tests. In Python, there are multiple ways to write tests, but a simple (and widely used) mechanism is usingpytest, where you write separate test functions (whose names have to start withtest_) usingassert. -
Furthermore, convention is that
pytesttests are put in separate files, and those files should be namedtest_something.py(wheresomethingcan be anything, but the file name must begin withtest_). These test files should then import your code from the code file. -
Save the above code in a file named
gadget.py, and then create a new filetest_gadget.py.test_gadget.py# pylint: disable=C0114, C0115, C0116, W0105, W0401
import pytest
# from the gadget.py file, get the function `gadget_cost`
from gadget import gadget_cost
def test_gadget_cost_small() -> None:
assert gadget_cost(1, "hi") == pytest.approx(0.6)
def test_gadget_cost_medium() -> None:
assert gadget_cost(10, "tech") == pytest.approx(6.5)
# Intentionally defined incorrectly to view a failing test(The first line, with the comment beginning with
# pylint:, instructspylintto not complain about missing doc strings, which are likely less important for test files than they are for code!) -
Pytest Conventions:
- Test functions must be named starting with
test_. - Use
assertto check expected results. It takes a boolean expression and causes a test failure if it evaluates toFalse. - Include only a single
assertper function, since each test function will stop running at the first assertion failure, so if you have many within a single function, it will only run to the first test failure. - For floating-point (any decimal in Python) results, use
pytest.approxfor comparison. This is analogous to Pyret'sis-roughly.
- Test functions must be named starting with
-
How to run tests:
- Save both files.
- In the VSCode Terminal (Terminal menu -> New Terminal), type:
And hit enter. (If this reports that the command is not found, please consult the previous day's instructions about installing
pytestpytest) - pytest will find and run all functions named
test_*, in all files namedtest_*and report results. - One of the tests is defined with an incorrect expected value. Fix
the expected value to
7and re-runpytestto see all tests succeed!
Creating Lists
- In Python, lists are created with square brackets, e.g.:
fruits = ["apple", "banana", "cherry", "date"] - Python has built-in functions like
filterandmapfor processing lists (just like you learned in Pyret, in Day 14), but you must wrap their results withlist():fruits_with_a = list(filter(lambda f: "a" in f, fruits))
# ["apple", "banana", "date"]
fruits_upper = list(map(lambda f: f.upper(), fruits))
# ["APPLE", "BANANA", "CHERRY", "DATE"] - Anonymous functions use
lambdain Python -- but these are more restricted than Pyret'slam, as the body can be a single expression only (i.e., you cannot have anif,for, variable assignment, etc, inside alambda).
Dataclasses
- Python's
@dataclassdecorator, combined with theclassfeature, allows you to define structured data types, similar to structured data using a single variant with Pyret'sdatadefinitions (conditional data is more complicated, and not covered here). Example:from dataclasses import dataclass
@dataclass
class Fruit:
name: str
color: str
Class Exercises (35 mins)
-
Function Syntax Practice
- Rewrite the following Pyret function in Python:
Be sure to follow full design recipe, including a docstring and test cases (in a separate file).
fun greet(name :: String) -> String:
doc: "produces Hello, name!"
"Hello, " + name + "!"
where:
greet("Alice") is "Hello, Alice!"
greet("Bob") is "Hello, Bob!"
end - Now, design a Python function that takes a name and an age, and returns a string like
"Alice is 20 years old."
- Rewrite the following Pyret function in Python:
-
Conditionals
- Translate this Pyret function to Python:
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
- Translate this Pyret function to Python:
-
Return Statement
- What happens if you forget the
returnstatement in a Python function? Try it and observe the result.
- What happens if you forget the
-
Create a list of integers from 1 to 10.
-
Write an expression using
mapthat produces a list of their squares. -
Design a function
all_even(nums: list) -> listthat returns a list of all even numbers from the input list. -
Design a function
capitalize_all(words: list) -> listthat returns a list of all words capitalized. -
Use
mapandlambdato produce a list of the lengths of each word in["hello", "world", "python"]. -
What happens if you forget to wrap the result in
list()? Try it and note the error. -
Define a
@dataclasscalledBookwith fieldstitle(str),author(str), andpages(int). -
Design a function
long_books(books: list) -> listthat returns a list of titles of books with more than 300 pages. Remember to always include docstring, type signature, and pytest tests. -
Create a list of
Bookinstances and uselong_booksto filter them. -
Design a function
filter_by_author(books: list, author: str) -> listthat returns a list of titles of books by the given author.
Wrap-up (5 mins)
- Pyret and Pyret share many aspects, but differ in some ways as well.