What questions came up?
count_votes_for()
¶From the course page:
from typing import List
from testlight import * # testlight.py must be in the same folder as this python script
def count_votes_for(who: str, votes: List[str]) -> int:
"""count how many times who is in the votes list"""
total = 0
for name in votes:
if name == who:
total = total + 1
return total
def test_count_votes():
test("no votes", count_votes_for("Kathi", []), 0)
test("none for candidates", count_votes_for("Kathi", ["Will", "Will"]), 0)
# run our tests
test_count_votes()
Tl;dr - There is not a concept of a block comment, like in Pyret. You may be able to turn your code into a string using """your code here"""
. But this does not work if you have strings containted in your code.
However since we are working with PyCharm, there is a feature that allows you to comment out a number of lines of code. To add or remove a "block comment", do one of the following:
Highlight the code you want to comment out and either:
These commands will individually comment out selected lines of code.
return
, rewriting test_record_votes()
¶What happens if your function doesn't return
an answer?
You should use the function on one line, then test that you observe the expected change in the test expression. An example of this is below.
We also noted that, if we created an empty votesList outside the test_record_votes()
function, running out tests twice could cause them to fail. This is becuase changes in data that occured during tests would be repeated and recorded.
def record_vote(votes: List[str], name: str):
"""add name to list of votes"""
votes.append(name)
def test_record_votes():
votesList = [] # set up test data inside the test function
record_vote(votesList, "Will")
test("vote for will", "Will" in votesList, True)
test("no votes for Kathi", "Kathi" not in votesList, True)
record_vote(votesList, "Kathi")
test("check Kathi", "Kathi" in votesList, True)
# run our tests
test_record_votes()
def record_vote(votes: List[str], name: str):
"""add name to list of votes"""
votes.append(name)
votesList = ["A", "B", "C"]
def test_record_votes():
votesList = [] # set up test data inside the test function
record_vote(votesList, "Will")
test("vote for will", "Will" in votesList, True)
test("vote count for will", count_votes_for("Will", votesList), 1)
test("no votes for Kathi", "Kathi" not in votesList, True)
record_vote(votesList, "Kathi")
test("check Kathi", "Kathi" in votesList, True)
# run our tests
test_record_votes()
# print our votesList, expecting ["Will", "Kathi"]
print(votesList)
Uh oh. Why are we getting votesList = ["A", "B", "C"]
when we changed our data in test_record_votes()
?
Lets look at the Environment and the Heap:
Environment (Known Names)
votesList => loc 1000
test_record_votes => function...
Python then grabs 4 'slots', and fills them in.
Python Heap
label | slot |
---|---|
1000 | "A" |
1001 | "B" |
1002 | "C" |
1003 |
|
When we call test_record_votes()
, a temporary area gets set up in the enviornment.
Environment (Known Names)
votesList => loc 1000
test_record_votes => function...
----------(temp area)--------
votesList => loc 1004
Python then grabs an additional 4 'slots' and starts filling out the changes we made inside the test_record_votes()
function.
Python Heap
label | slot |
---|---|
1000 | "A" |
1001 | "B" |
1002 | "C" |
1003 |
|
1004 | "Will" |
1005 | "Kathi" |
1006 |
|
1007 |
|
But once the call to test_record_votes()
is finished, the temporary area disappears (e.g. votesList => loc 1004) and we are left with
Environment (Known Names)
votesList => loc 1000
test_record_votes => function...
Python Heap
label | slot |
---|---|
1000 | "A" |
1001 | "B" |
1002 | "C" |
1003 |
|
def count_z(words: List[str]) -> int:
"""count how many z are in the list of words"""
total = 0
for w in words:
for c in w:
if c == "z":
total = total + 1
return total
def test_z():
test("z", count_z(["pizza", "zebra", "Wednesday"]), 3)
test_z()
data
block like structures in Python¶What if we wanted to indicate a position in Python? We did a similar exercise in Pyret when we created a date()
data type. We can achieve the same effect in Python using the @dataclass
notation.
The @dataclass
command is part of a different paradigm of programming called object-oriented programming. You might be familiar with some concepts of this if you have ever programmed with Java. If you're interested in learning more about the similarities and differences, you can read more about it here.
from dataclasses import dataclass
@dataclass
class Posn:
x: int
y: int
Posn(3, 4)
@dataclass
to make a ToDo List¶We might think about how we can use this idea of a @dataclass
to make a ToDo list. We might also want to search our ToDoList by description. We can do this as so:
from datetime import date
from typing import List
# set up ToDoTiem data
# make a short (3-item) ToDoList
# write a function find-items that takes search string and a list of
# ToDoItem and returns list of items whose descr contains search string
# define a class ToDoItem
@dataclass
class ToDoItem:
descr: str
due: date
tags: List[str]
# create and example todo list
MyTD = [ToDoItem("buy milk", date(2019, 7, 27), ["shopping", "home"]),
ToDoItem("grade hwk", date(2019, 7, 27), ["teaching"]),
ToDoItem("meet students", date(2019, 7, 26), ["research"])]
# We can do this three ways: using a lambda, using a helper function, and using
# a for look
# 1. using a lambda
def find_items_lambda(term: str, TDL: List[ToDoItem]) -> List[ToDoItem]:
"""consumes a term and a list of todoitems, returns todoitems that contain
the term in their description"""
return list(filter(lambda item: term in item.descr, TDL))
# 2. using a helper function
def find_items_w_helper(term: str, TDL: List[ToDoItem]) -> List[ToDoItem]:
"""consumes a term and a list of todoitems, returns todoitems that contain
the term in their description"""
def has_descr(item: ToDoItem): return term in item.descr
return list(filter(has_descr, TDL))
# 3. using a for loop
def find_items_for_loop(term: str, TDL: List[ToDoItem]) -> List[ToDoItem]:
matches = []
for item in TDL:
if term in item.descr:
matches.append(item)
return matches
# look for milk in MyTD
print(find_items_lambda("milk", MyTD))
print(find_items_w_helper("milk", MyTD))
print(find_items_for_loop("milk", MyTD))
# are they all the same? Yes.
print(find_items_lambda("milk", MyTD) ==
find_items_w_helper("milk", MyTD) ==
find_items_for_loop("milk", MyTD))
Let's say we want to remove an item. We can write a function that removes the first occurance that matches the description we provide.
MyTD = [ToDoItem("buy milk", date(2019, 7, 27), ["shopping", "home"]),
ToDoItem("grade hwk", date(2019, 7, 27), ["teaching"]),
ToDoItem("meet students", date(2019, 7, 26), ["research"])]
def rem_item(des: str, TDL: List[ToDoItem]) -> List[ToDoItem]:
"""remove item with given descr from the ToDoList, it it is there"""
item_to_remove = find_items(des, TDL)[0]
TDL.remove(item_to_remove)
return MyTD
rem_item("milk", MyTD)
Now what if we wanted to remove all matching occurances of the description we provided? We can rewrite our function above to do so:
MyTD = [ToDoItem("buy milk", date(2019, 7, 27), ["shopping", "home"]),
ToDoItem("grade hwk", date(2019, 7, 27), ["teaching"]),
ToDoItem("meet students", date(2019, 7, 26), ["research"])]
def rem_item_all(des: str, TDL: List[ToDoItem]) -> List[ToDoItem]:
"""remove items with given descr from the ToDoList, if it is there"""
list_of_matches = find_items(des, TDL)
for item_to_remove in list_of_matches:
TDL.remove(item_to_remove)
return TDL
rem_item_all("e", MyTD)
Now what if we wanted to update the due date on an item with a specific description? We'll assume, for the moment, there there will be one and only one element that fits the description we provide.
NB: This is actually a function that Will has to write to update our grading system whenever anyone gets an extension.
There were serveral ways the class thought about doing this, which are shown below:
map
MyTD = [ToDoItem("buy milk", date(2019, 7, 27), ["shopping", "home"]),
ToDoItem("grade hwk", date(2019, 7, 27), ["teaching"]),
ToDoItem("meet students", date(2019, 7, 26), ["research"])]
def update_duedate_1(des: str, new_date: date, TDL: List[ToDoItem]) -> List[ToDoItem]:
"""change the due date on the item with given descr"""
def update_item(item: ToDoItem) -> ToDoItem:
if des in item.descr:
return ToDoItem(des, new_date, item.tags)
else:
return item
return list(map(update_item, TDL))
print("Before update:\n")
print(MyTD)
# lets suppose I don't have to buy milk until the new year
print("\n After update:")
update_duedate_1("milk", date(2020, 1, 1), MyTD)
MyTD = [ToDoItem("buy milk", date(2019, 7, 27), ["shopping", "home"]),
ToDoItem("grade hwk", date(2019, 7, 27), ["teaching"]),
ToDoItem("meet students", date(2019, 7, 26), ["research"])]
def update_duedate_2(des: str, new_date: date, TDL: List[ToDoItem]) -> List[ToDoItem]:
"""change the due date on the item with given descr"""
item = find_items_lambda(des, TDL)[0]
TDL.remove(item)
TDL.append(ToDoItem(des, new_date, item.tags))
return TDL
print("Before update:\n")
print(MyTD)
# lets suppose I don't have to buy milk until the new year
print("\n After update:")
update_duedate_2("milk", date(2020, 1, 1), MyTD)
MyTD = [ToDoItem("buy milk", date(2019, 7, 27), ["shopping", "home"]),
ToDoItem("grade hwk", date(2019, 7, 27), ["teaching"]),
ToDoItem("meet students", date(2019, 7, 26), ["research"])]
def update_duedate_3(des: str, new_date: date, TDL: List[ToDoItem]) -> List[ToDoItem]:
"""change the due date on the item with given descr"""
item = find_items(des, TDL)[0]
#print(item)
item.due = new_date
#print(item)
#print(TDL)
return TDL
print("Before update:\n")
print(MyTD)
# lets suppose I don't have to buy milk until the new year
print("\n After update:")
update_duedate_3("milk", date(2020, 1, 1), MyTD)
These three approaches are different in very subtle ways. We will see next class :)