ICS 33 Fall 2024
Exercise Set 7
Due date and time: Monday, December 2, 11:59pm
Getting started
First of all, be sure that you've read the Reinforcement Exercises page, which explains everything you'll need to know, generally, about how the reinforcement exercises will work this quarter. Make sure you read that page before you continue with this one.
Problem 1 (2 points)
During our discussion about Functional Programming, we implemented a function we named partially_call
, which offered the ability to partially call a function, allowing some of its arguments to be passed initially, with others to be passed later. Our solution stopped a little short, though, because it had no support for keyword arguments.
Re-implement partially_call
so that it can handle a partial call to any Python function, supporting the passing of both positional and keyword arguments either in a partial call or in a "completing" call afterward. To the extent that the original function can accept the combination of arguments passed to it, it should be possible to use partially_call
to do so, as if the arguments had all been passed at once; when it can't, the call should fail, just as it would normally.
Some examples of your function's expected behavior follow.
>>> def multiply(n, m):
... return n * m
...
>>> multiply_by_three = partially_call(multiply, 3)
>>> multiply_by_three(8)
24
>>> multiply_by_three_kw = partially_call(multiply, m = 3)
>>> multiply_by_three_kw(n = 8)
24 # keyword arguments can be passed to functions that accept them
>>> multiply_by_three_kw(8)
24 # positional and keyword arguments can be combined
>>> multiply_by_three_kw(n = 8, m = 4)
32 # keyword arguments in a partial call can be replaced subsequently,
# with later-passed versions replacing earlier-passed ones
>>> argless_multiply = partially_call(multiply, 3, 8)
>>> argless_multiply()
24
>>> argless_multiply(3)
Traceback (most recent call last):
...
TypeError: multiply() takes 2 positional arguments but 3 were given
>>> invalid_multiply = partially_call(multiply, q = 5)
>>> invalid_multiply(n = 3, m = 7)
Traceback (most recent call last):
...
TypeError: multiply() got an unexpected keyword argument 'q'
# arguments aren't invalid until the function is actually called
Limitations
The Python standard library is entirely off-limits in this problem. Your function must not require any module to be imported.
What to submit
Submit one Python script named problem1.py
, which contains your function and nothing else. Neither docstrings, comments, nor type annotations are required, since we've all agreed already on what problems we're solving here.
There is no credit being offered for writing automated tests — though you certainly might want to, since that's a great way to ensure that your code works as you expect — and we'd prefer you not submit them even if you do.
Problem 2 (2 points)
We saw when discussing Decorators that we can decorate the same function with more than one decorator, and that Python specifies an order in which they're processed (i.e., the order in which we write the decorators affects the outcome). In no more than a couple of sentences each, answer the following two questions about the use of multiple decorators on the same function.
What to submit
Submit one PDF named problem2.pdf
, which contains your answer to these two questions.
Problem 3 (4 points)
Write a parameterized decorator @cached
that transforms a Python function into one that caches its results, so that subsequent calls with equivalent arguments can be fulfilled by looking up the previous result in a cache, rather than re-calculating the result by calling the original function. One argument is required to be passed to @cached
, an integer specifying the size of the cache (i.e., the number of different previous results to store in the cache).
>>> import time
>>> @cached(20)
... def expensive_square(n):
... time.sleep(10)
... return n * n
... # expensive_square will maintain a cache of 20 previous results.
>>> expensive_square(3)
9 # This will take about 10 seconds, because of the sleep.
>>> expensive_square(4)
16 # Likewise, this will take about 10 seconds, because of the sleep.
>>> expensive_square(3)
9 # This will be (more or less) instantaneous, because its result has
# already been stored in the cache.
Of course, results aren't returned from the cache indiscriminately; only when a subsequent call has arguments — including both positional and keyword arguments — that are equivalent to a previous call will the cache be utilized. That's why, in the example above, only the second call to expensive_square(3)
was able to run in fewer than ten seconds; the cache can't know a result until it's seen it already.
There are four basic requirements that need to be met.
Limitations
The Python standard library is entirely off-limits in this problem, except for the random
module, which you may find useful for choosing results to evict from the cache. Your function must not require any other module to be imported.
What to submit
Submit one Python script named problem3.py
, which contains your decorator, any helper functions or classes required by it, and nothing else. Neither docstrings, comments, nor type annotations are required, since we've all agreed already on what problems we're solving here.
There is no credit being offered for writing automated tests — though you certainly might want to, since that's a great way to ensure that your code works as you expect — and we'd prefer you not submit them even if you do.
Problem 4 (2 points)
When we initially used Decorators to transform functions, we saw that there are limitations on the kinds of transformations they can perform. They can add behavior that occurs before and/or after the original function's body, or they can replace the original function's body with something else entirely, but that's where the line is drawn; they can't modify the body of the original function.
When a decorator appears above a class, what are the limitations on the transformations it can perform? In other words, in what ways can a class be modified by a decorator?
What to submit
Submit one PDF named problem4.pdf
, which contains your answer to this question.
Deliverables
In Canvas, you'll find a separate submission area for each problem. Submit your solution to each problem into the appropriate submission area. Be sure that you're submitting the correct file into the correct area (i.e., submitting your Problem 1 solution to the area for Problem 1, and so on). Under no circumstances will we offer credit for files submitted in the incorrect area.
Submit each file as-is, without putting it into a Zip file or arranging it in any other way. If we asked for a PDF, for example, all we want is a PDF; no more, no less. If you submit something other than what we asked for (e.g., a text file when we asked for a PDF, even if its filename ends in .pdf
), we will not be offering you any credit on the submission. There are no exceptions to this rule.
Of course, you should also be aware that you're responsible for submitting precisely the version of your work that you want graded. We won't regrade an exercise simply because you submitted the wrong version accidentally.
Can I submit after the deadline?
Unlike some of the projects in this course, the reinforcement exercises cannot be submitted after the deadline; there is no late policy for these. Each is worth only 2% of your grade, with the lowest score dropped — see the Reinforcement Exercises page for details — so it's not a disaster if you miss one of them along the way.
You're responsible for making a submission in order to receive credit, which means you'll want to be sure that you've remembered to submit your work and verify in Canvas that it's been received. A later claim of having forgotten to submit your work or having misremembered the due date will not be grounds for a resubmission under any circumstances.
What do I do if Canvas adjusts my filename?
Canvas will sometimes modify your filenames when you submit them (e.g., by adding a numbering scheme like -1 or a long sequence of hexadecimal digits to its name). In general, this is fine; as long as the file you submitted has the correct name prior to submission, we'll be able to obtain it with that same name, even if Canvas adjusts it.