.py
files containing your work. (If you upload a screenshot or other file format, you won't get credit.)This homework assignment must be submitted in Gradescope by Noon central time on Tuesday 1 February 2022.
Collaboration is prohibited, and you may only access resources (books, online, etc.) listed below.
The course materials you may refer to for this homework are:
This homework is a bit lighter than usual, because I know you're busy with Project 1.
This homework assignment has a single problem, number 2. The grading breakdown is:
Points | Item |
---|---|
2 | Autograder |
4 | Problem 2 |
6 | Total |
The part marked "autograder" reflects points assigned to your submission based on some simple automated checks for Python syntax, etc.. The result of these checks is shown immediately after you submit.
Ask your instructor or TA a question by email, in office hours, or on discord.
In Gradescope, the score assigned to your homework submission by the autograder (checking for syntax and docstrings) will be recorded as "Problem 1". Therefore, the numbering of the actual problems begins with 2.
Below you'll find code that defines a class WriteOnceDict
that is a subclass of dict
which doesn't allow values associated to existing keys to be changed, nor even for an attempt to be made. That is, if an instance d
of WriteOnceDict
already contains a key k
, then
d[k] = value
will raise an exception.
class WriteOnceDict(dict):
"""
Dictionary where an existing value cannot be changed.
"""
def __setitem__(self,k,v):
"Create new key `k` with value `v`; if `k` is already a key, raise ValueError"
if k in self:
raise ValueError("Attempt to change value associated to existing key {}. This is not allowed by {}.".format(
k,
self.__class__.__name__ # name of this class, as a string
))
else:
super().__setitem__(k,v) # Call `dict` class setitem method
# Note: can't just say self[k]=v since that will call this method!
# Also, super() doesn't allow item assignment, e.g. super()[k]=v fails.
Here's an example of how WriteOnceDict
works:
d = WriteOnceDict() # new empty WriteOnceDict container
d["a"] = "Hello!" # create key
d[15] = True # create key
d["a"] = "This will not work!" # Attempt to change value of existing key
Unfortunately, the WriteOnceDict
class doesn't quite work as promised. The dict
class has another method called update
that can also change values associated to keys. Specifically, if you call d.update(e)
, where e
is another dictionary, then every key-value pair from e
will be copied into d
, overwriting existing values if necessary. And in the implementation of WriteOnceDict
above, the inherited update
method can still be used to make changes. Here's an example:
d = WriteOnceDict() # new empty WriteOnceDict container
d["asdf"] = "I should not change"
d.update( {"asdf":"And yet, here we are."} ) # No error! It makes changes despite "write once" claim
# Let's check that the value associated to "asdf" has actually changed
d["asdf"]
WriteOnceDict
so you can't change values with .update(...)
either¶Take the code for WriteOnceDict
above, copy it into a file hwk3prob2.py
and fix the issue described above by adding an update
method. This method should expect a single dictionary as its argument, and should copy the key-value pairs into self
, but refuse to modify any existing keys while doing so. Below you'll find code showing the expected behavior of your revised WriteOnceDict
class.
# Demo of the desired behavior after your changes
d = WriteOnceDict()
d["asdf"] = "I should not change"
d.update( {"asdf":"And yet, here we are."} ) # Now, this line will raise ValueError!
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-57-454d90a0c660> in <module>
2 d = WriteOnceDict()
3 d["asdf"] = "I should not change"
----> 4 d.update( {"asdf":"And yet, here we are."} ) # Now, this line will raise ValueError!
[ ... part of output redacted because it shows the solution ... ]
ValueError: Attempt to change value associated to existing key asdf. This is not allowed by WriteOnceDict.
Note the new method update()
below.
class WriteOnceDict(dict):
"""
Dictionary where an existing value cannot be changed.
"""
def __setitem__(self,k,v):
"Create new key `k` with value `v`; if `k` is already a key, raise ValueError"
if k in self:
raise ValueError("Attempt to change value associated to existing key {}. This is not allowed by {}.".format(
k,
self.__class__.__name__ # name of this class, as a string
))
else:
super().__setitem__(k,v) # Call `dict` class setitem method
# Note: can't just say self[k]=v since that will call this method!
# Also, super() doesn't allow item assignment, e.g. super()[k]=v fails.
def update(self, other):
"""Add key-value pairs from `other` to this dictionary, raising ValueError if any of those
keys already exist"""
for key in other:
self[key] = other # calls our __setitem__ method above, hence won't overwrite a key
Remarks:
dict.update
does not call dict.__setitem__
itself. (If it did, then we wouldn't need to make any changes to get WriteOnceDict
to work.)update
, but the solution above has the advantage that the error message only appears in one place (hence is easier to change if needed).d = WriteOnceDict() # new empty WriteOnceDict container
d["asdf"] = "I should not change"
d.update( {"asdf":"This attempt to change will be in vain."} ) # This line should now give us an error (which we want!)