Class summary:   Updating Data and Data Equality
1 Updating Data:   Revising Due Dates
1.1 An Update Function
2 When are To  Do  Items Equal?

Class summary: Updating Data and Data Equality

Copyright (c) 2017 Kathi Fisler

Let’s start again with our to-do list setup from last week:

  @dataclass

  class ToDoItem:

    descr : str

    due : date

    tags : list

  

  milk_item = ToDoItem("buy milk", date(2018, 7, 27), ["shopping", "home"])

  grading_item = ToDoItem("grade hwk", date(2018, 7, 28), ["teaching"])

  paper_item = ToDoItem("meet students", date(2018, 7, 26), ["research"])

  

  todo_list = [grading_item, milk_item, paper_item]

We have already seen how to add and remove items from our todolist. What if we want to update the due date of an item?

1 Updating Data: Revising Due Dates

In Python, we update a field of a dataclass by using = to give a new value to the field within the value. For example:

  milk_item.due = date(11, 15, 2018)

  paper_item.descr = "meet Jack"

Once we do this, the original value is gone, replaced by the new value. We can see this by accessing the field, or by looking in the dictionary window in Pycharm:

  print(milk_item.due)

What if we now look at the entire todo_list? Will milk_item have the original or the new date? The new one. We modified milk_item, which was in the list.

1.1 An Update Function

Let’s now write a function to update the date in a ToDoItem.

  def update_duedate(forItem : ToDoItem, new_date : date):

      """change due date on given item to the new date"""

      forItem.due = new_date

What would the test for this look like?

  def test_update():

      item1 = ToDoItem("register", date(11, 9, 2018), ["school"])

      test("item1 check", update_duedate(item1, date(11, 15, 2018)), ???)

How can we fill in the ??? here? update_duedate returns None, so what is there to test?

In this case, we have to do the update on a separate line, then test that the value in the due field changed. We should also test that the others fields did not change.

  def test_update():

      item1 = ToDoItem("register", date(2018, 11, 9), ["school"])

      update_duedate(item1, date(2018, 11, 15))

      test("due check", item1.due, date(2018, 11, 15))

      test("descr check", item1.descr, "register")

      test("tags check", item1.tags,  ["school"])

Hmm, should we perhaps have had update_duedate return the item, so we could test it more directly?

  def test_update():

      item2 = ToDoItem("register", date(2018, 11, 9), ["school"])

      test("item1 check", update_duedate(item2, date(2018, 11, 15)),

           ToDoItem("register", date(2018, 11, 15), ["school"]))

It isn’t clear that this version is nicer to write, and if the test fails, it’s harder to track down what went wrong. Testing larger data piecemeal (as we did by testing each field separately) can make working with testing output more manageable.

2 When are ToDoItems Equal?

You have a very important phone call to make, so you made multiple todo items for it to make sure you don’t miss it (by the third one, you were tired of typing and just reused the earlier one).

  call1 = ToDoItem("call fred", date(2018, 11, 15), ["urgent"])

  call2 = ToDoItem("call fred", date(2018, 11, 15), ["urgent"])

  call3 = call1

What will the dictionary look like for this?

  call1 --> ToDoItem("call fred", date(2018, 11, 15), ["urgent"])

  call2 --> ToDoItem("call fred", date(2018, 11, 15), ["urgent"])

  call3 --> ToDoItem("call fred", date(2018, 11, 15), ["urgent"])

We can ask Python which of these values are the same, using ==. For example:

  >>> call1 == call3

  True

  >>> call2 == call1

  True

  >>> call3 == call1

  True

This works because == asks whether two expressions have the same contents.

Now, let’s update the date on call1 to date(2018, 11, 13). What should change? Certainly call1, but what about call2 and call3?

call3 changes, but not call2. Here’s the new dictionary.

  call1 --> ToDoItem("call fred", date(2018, 11, 13), ["urgent"])

  call2 --> ToDoItem("call fred", date(2018, 11, 15), ["urgent"])

  call3 --> ToDoItem("call fred", date(2018, 11, 13), ["urgent"])

  call4 --> ToDoItem("call fred", date(2018, 11, 15), ["urgent"])

This makes some intuitive sense – we said call3 was call1, but nothing in our code connected call2 to either call1 or call3.

But it raises a question: what does equality mean? We checked before that all of these items were "the same" via ==, but changes to call1 affect the others differently.

Just to make these clear, let’s add another call:

  call4 = ToDoItem("call fred", date(2018, 11, 15), ["urgent"])

Now, call4 == call2, but if we change call2, we don’t expect call2 to change (because nothing in the code ties them together, as happens for call1 and call3).

What we are seeing here is that there are two different kinds of "equality" relationships that can exist between two names:

The == operation that we have used so far returns True for both relationships. If we want to tell the two relationships apart in code, we use a different comparison operator, called is:

  >>> call1 is call3

  True

  >>> call2 is call4

  False

  >>> call2 == call4

  True

Both is and == are symmetric relationships: changes to either call3 or call1 changes the other.

But how does the difference between these two kinds of equality show up in the dictionary?

The dictionary lets you see == relationships, but it seems to be missing the is-relationship between call1 and call3. Doesn’t it?

Yep, which is a motivation to come back next class ...