CSCI 0050: Even more Python Notes, 7/25/19

TL;DR

  • If you need help finding where your function isn't behaving as it should, check out the 'debugger' in section 1.2
  • Want to make sure you get all the style points for your Python assignments? Check out the style guide here.
  • If you're interested in the methodology behind Python (e.g. why certain style decisions are made), you can check out this link.
  • We learned how to make our programs share, an important concept when we can mutate our data.

From last class

In [1]:
from testlight import *

# program tracing practice
def count_char(find_char: str, words: list) -> list:
    """count how many times a given char appears in a list of words"""
    count = 0
    for w in words:
        for char in w:
            # memory point 2 (when processing the "u" in "purple")
            if char == find_char:
                count = count + 1
    return count

def count_tests():
    """tests to show expected results of function"""
    test(count_char("p", ["happy", "purple"]), 4)
    test(count_char("p", ["summer"]), 0)
    
# memory point 1
count_char("p", ["happy", "purple"])
# memroy point 3
Out[1]:
4

View @ memory point 1

Environment (Known Names)

count_char => function
count_tests => function
----------(temp area)--------
find_char => "p"
words => loc 1000

Python Heap

label slot
1000 ["happy"
1001 "purple"]
1002 " "

Intermediate view

Environment (Known Names)

count_char => function
count_tests => function
----------(temp area)--------
find_char => "p"
words => loc 1000
count => 0
w => "happy" # @ line 8 (first time through)
char => "h" # @ line 9 (first time through)
char => "a" # @ line 9 (second time through)
char => "p" # @ line 9 (third time through)
count => 1
char => "p" # @ line 9 (fourth time through)
count => 2
char => "y" # @ line 9 (fifth time through)
w => "purple" # @ line 8 (second time through)
char => "p" # @ line 9 (sixth time through)
count => 3
char => "u" # @ line 9 (seventh time through)

Python Heap

label slot
1000 ["happy"
1001 "purple"]
1002 " "

View @ memory point 2

Environment (Known Names)

count_char => function
count_tests => function
----------(temp area)--------
find_char => "p"
words => loc 1000
w => "purple"
char => "u"
count => 3

Python Heap

label slot
1000 ["happy"
1001 "purple"]
1002 " "

View @ memory point 3

Once the call is completed, the temporary area is erased.

Environment (Known Names)

count_char => function
count_tests => function

Python Heap

label slot
1000 ["happy"
1001 "purple"]
1002 " "

Programming tip: Using the debugger

Professional programmers don't hand trace their programs.

Okay... maybe some do, but you don't have to! Since we're working with PyCharm, there's a nice built-in feature for doing this with your programs.

Under the Run menu, there is an option to "Step-through" the program. Click this to open the debugger panel.

There are a set of icons under the debugger panel (at the bottom of the PyCharm window).

  • The 'bent-arrow' icon tells Pycharm to skip over a line. You should use this when your from testlight import * line is highlighted.
  • The downward pointing arrow (called "step-into") will step through your code, line by line. You can watch data structures appear in the "variables" panel (usually to the left).

Learning to share: updating due dates from last class

The below code was copied from last class. Would the result produce the same value for each different update_duedate function?

In [2]:
from typing import List
from testlight import *
from dataclasses import dataclass
from datetime import date

@dataclass
class ToDoItem:
    descr: str
    due: date
    tags: List[str]

milkTD = ToDoItem("buy milk", date(2019, 7, 27), ["shopping", "home"])
gradeTD = ToDoItem("grade hwk", date(2019, 7, 27), ["teaching"])

myTDL = [milkTD,
        gradeTD,
        ToDoItem("meet students", date(2019, 7, 26), ["research"])
        ]

spouseTDL = [milkTD]

# memory point 1

def find_items(term: str, TDL: List[ToDoItem]) -> List[ToDoItem]:
    return list(filter(lambda item: term in item.descr, TDL))

def update_duedate1(des: str, new_date: date, TDL: List[ToDoItem]) -> List[ToDoItem]:
    def update_item(item: ToDoItem) -> ToDoItem:
        if des == item.descr:
            return ToDoItem(des, new_date, item.tags)
        else:
            return item
    return list(map(update_item, TDL))

def update_duedate2(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]
    TDL.remove(item)
    TDL.append(ToDoItem(des, new_date, item.tags))
    return TDL

def update_duedate3(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]
    item.due = new_date
    return TDL

print(myTDL)
print("\n")
update_duedate1("buy milk", date(2018, 8, 5), myTDL)
print(myTDL)
print("\n")
print("milkTD due date is " + str(milkTD.due))
print("spouseTD is " + str(spouseTDL))
[ToDoItem(descr='buy milk', due=datetime.date(2019, 7, 27), tags=['shopping', 'home']), ToDoItem(descr='grade hwk', due=datetime.date(2019, 7, 27), tags=['teaching']), ToDoItem(descr='meet students', due=datetime.date(2019, 7, 26), tags=['research'])]


[ToDoItem(descr='buy milk', due=datetime.date(2019, 7, 27), tags=['shopping', 'home']), ToDoItem(descr='grade hwk', due=datetime.date(2019, 7, 27), tags=['teaching']), ToDoItem(descr='meet students', due=datetime.date(2019, 7, 26), tags=['research'])]


milkTD due date is 2019-07-27
spouseTD is [ToDoItem(descr='buy milk', due=datetime.date(2019, 7, 27), tags=['shopping', 'home'])]
In [3]:
print(myTDL)
print("\n")
update_duedate2("buy milk", date(2018, 8, 5), myTDL)
print(myTDL)
print("\n")
print("milkTD due date is " + str(milkTD.due))
print("spouseTD is " + str(spouseTDL))
[ToDoItem(descr='buy milk', due=datetime.date(2019, 7, 27), tags=['shopping', 'home']), ToDoItem(descr='grade hwk', due=datetime.date(2019, 7, 27), tags=['teaching']), ToDoItem(descr='meet students', due=datetime.date(2019, 7, 26), tags=['research'])]


[ToDoItem(descr='grade hwk', due=datetime.date(2019, 7, 27), tags=['teaching']), ToDoItem(descr='meet students', due=datetime.date(2019, 7, 26), tags=['research']), ToDoItem(descr='buy milk', due=datetime.date(2018, 8, 5), tags=['shopping', 'home'])]


milkTD due date is 2019-07-27
spouseTD is [ToDoItem(descr='buy milk', due=datetime.date(2019, 7, 27), tags=['shopping', 'home'])]
In [4]:
print(myTDL)
print("\n")
update_duedate3("buy milk", date(2018, 8, 5), myTDL)
print(myTDL)
print("\n")
print("milkTD due date is " + str(milkTD.due))
print("spouseTD is " + str(spouseTDL))
[ToDoItem(descr='grade hwk', due=datetime.date(2019, 7, 27), tags=['teaching']), ToDoItem(descr='meet students', due=datetime.date(2019, 7, 26), tags=['research']), ToDoItem(descr='buy milk', due=datetime.date(2018, 8, 5), tags=['shopping', 'home'])]


[ToDoItem(descr='grade hwk', due=datetime.date(2019, 7, 27), tags=['teaching']), ToDoItem(descr='meet students', due=datetime.date(2019, 7, 26), tags=['research']), ToDoItem(descr='buy milk', due=datetime.date(2018, 8, 5), tags=['shopping', 'home'])]


milkTD due date is 2019-07-27
spouseTD is [ToDoItem(descr='buy milk', due=datetime.date(2019, 7, 27), tags=['shopping', 'home'])]

Tracing the update_duedate function

Looks like update_duedate1 didn't update the due date, update_duedate2 updated and moved the milk todo item to the end of the list, and update_duedate3 updated the milk in place. These are three very different behaviors!

Tracing update_duedate3

Before running this function, we will have Environment (Known Names)

milkTD => loc 1004
gradeTD => loc 1008
myTDL => loc 1013
spoundsTDL => loc 1017
----------(temp area)--------

Python Heap

label slot
1000 date(2019, 7, 27)
1001 ["shopping",
1002 "home"
1003 ]
1004 ToDoItem("buy milk", loc 1000, loc 1001)
1005 date(2019, 7, 27)
1006 ["teaching",
1007 ]
1008 ToDoItem("grade hwk", loc 1005, loc 1006)
1009 date(2019, 7, 26)
1010 ["research,
1011 ]
1012 ToDoItem("meet students", loc 1009, loc 1010)
1013 [loc 1004,
1014 loc 1008,
1015 loc 1012
1016 ]
1017 [loc 1004,
1018 ]

As an exercise we asked, at the prompt, how could we get "buy milk"? Some ways to do this were:

>>> "buy milk" # the obvious answer
"buy milk"
>>> milkTD.descr
"buy milk"
>>> myTDL[0].descr
"buy milk"
>>> spouseTDL[0].descr
"buy milk"

Notice that all of these ended up at the same location in memory (loc 1004).

However, if we were to write:

>>> milkTD.due
datetime.date(2019, 7, 27)
>>> gradeTD.due
datetime.date(2019, 7, 27)

While these return the same value, they don't point to the same place in our heap. gradeTD.due is in loc 1009 while milkTD.due is in loc 1005!

This illustrates that, in programming, there are two differnt possible meanings of equality.

  1. We have the concept of the same type and contents. An example of this would be gradeTD.due and milkTD.due.
  2. We now also have the concept of having the same memory location. An example of this is milkTD.descr and myTDL[0].descr.

Now, if we call update_duedate3.

Environment (Known Names)

milkTD => loc 1004
gradeTD => loc 1008
myTDL => loc 1013
spoundsTDL => loc 1017
----------(temp area)--------
des => "buy milk"
new_date => loc 1019
TDL => loc 1013
item => 1004

Python Heap

label slot
1000 date(2019, 7, 27)
1001 ["shopping",
1002 "home"
1003 ]
1004 ToDoItem("buy milk", loc 1000, loc 1001)
1005 date(2019, 7, 27)
1006 ["teaching",
1007 ]
1008 ToDoItem("grade hwk", loc 1005, loc 1006)
1009 date(2019, 7, 26)
1010 ["research,
1011 ]
1012 ToDoItem("meet students", loc 1009, loc 1010)
1013 [loc 1004,
1014 loc 1008,
1015 loc 1012
1016 ]
1017 [loc 1004,
1018 ]
1019 date(2018, 8, 5)
1020 [loc 1004] # result of filter
1011
1022

We then alter the information at loc 1004 (which, before the call, is ToDoItem("buy milk", loc 1000, loc 1001)). We change this to ToDoItem("buy milk", loc 1019, loc 1001), so the heap will now look like:

label slot
1000 date(2019, 7, 27)
1001 ["shopping",
1002 "home"
1003 ]
1004 ToDoItem("buy milk", loc 1020, loc 1001)
1005 date(2019, 7, 27)
1006 ["teaching",
1007 ]
1008 ToDoItem("grade hwk", loc 1005, loc 1006)
1009 date(2019, 7, 26)
1010 ["research,
1011 ]
1012 ToDoItem("meet students", loc 1009, loc 1010)
1013 [loc 1004,
1014 loc 1008,
1015 loc 1012
1016 ]
1017 [loc 1004,
1018 ]
1019 date(2018, 8, 5)
1020 [loc 1004] # result of filter
1021
1022

Tracing update_duedate2

First, the reference to loc 1004 in loc 1013 is eliminated. A new ToDoItem is also created in loc 1021, resulting in:

label slot
1000 date(2019, 7, 27)
1001 ["shopping",
1002 "home"
1003 ]
1004 ToDoItem("buy milk", loc 1020, loc 1001)
1005 date(2019, 7, 27)
1006 ["teaching",
1007 ]
1008 ToDoItem("grade hwk", loc 1005, loc 1006)
1009 date(2019, 7, 26)
1010 ["research,
1011 ]
1012 ToDoItem("meet students", loc 1009, loc 1010)
1013 [,
1014 loc 1008,
1015 loc 1012
1016 ]
1017 [loc 1004,
1018 ]
1019 date(2018, 8, 5)
1020 [loc 1004] # result of filter
1021 ToDoItem("buy milk", loc 1019, loc 1001
1022

A reference to this new item is created, which results in:

label slot
1000 date(2019, 7, 27)
1001 ["shopping",
1002 "home"
1003 ]
1004 ToDoItem("buy milk", loc 1020, loc 1001)
1005 date(2019, 7, 27)
1006 ["teaching",
1007 ]
1008 ToDoItem("grade hwk", loc 1005, loc 1006)
1009 date(2019, 7, 26)
1010 ["research,
1011 ]
1012 ToDoItem("meet students", loc 1009, loc 1010)
1013 [,
1014 loc 1008,
1015 loc 1012
1016 loc 1021]
1017 [loc 1004,
1018 ]
1019 date(2018, 8, 5)
1020 [loc 1004] # result of filter
1021 ToDoItem("buy milk", loc 1019, loc 1001
1022

Where loc 1016 now points to the new ToDoItem at loc 1021.

Tracing update_duedate1

When we do map, we create a new list. We're not doing this in "gory" detail, so map is condensed to one location.

label slot
1000 date(2019, 7, 27)
1001 ["shopping",
1002 "home"
1003 ]
1004 ToDoItem("buy milk", loc 1020, loc 1001)
1005 date(2019, 7, 27)
1006 ["teaching",
1007 ]
1008 ToDoItem("grade hwk", loc 1005, loc 1006)
1009 date(2019, 7, 26)
1010 ["research,
1011 ]
1012 ToDoItem("meet students", loc 1009, loc 1010)
1013 [,
1014 loc 1008,
1015 loc 1012
1016 loc 1021]
1017 [loc 1004,
1018 ]
1019 date(2018, 8, 5)
1020 [loc 1004] # result of filter
1021 ToDoItem("buy milk", loc 1019, loc 1001
1022 [loc 1008, loc 1012, loc 1026]

The assignment of loc 1026 is arbitrary, the point here is that we return loc 1022 from update_duedate1. Nothing in the map call of update_duedate1 touches the original data at loc 1013. Hence, when we run it we won't observe any difference.

However, we can fix this problem in a simple step.

In [6]:
def update_duedate1(des: str, new_date: date, TDL: List[ToDoItem]) -> List[ToDoItem]:
    def update_item(item: ToDoItem) -> ToDoItem:
        if des == item.descr:
            return ToDoItem(des, new_date, item.tags)
        else:
            return item
    TDL = list(map(update_item, TDL))
    return TDL

update_duedate1("buy milk", date(2018, 8, 5), myTDL)
Out[6]:
[ToDoItem(descr='grade hwk', due=datetime.date(2019, 7, 27), tags=['teaching']),
 ToDoItem(descr='meet students', due=datetime.date(2019, 7, 26), tags=['research']),
 ToDoItem(descr='buy milk', due=datetime.date(2018, 8, 5), tags=['shopping', 'home'])]