178 lines
4.2 KiB
Markdown
178 lines
4.2 KiB
Markdown
\[ [Index](index.md) | [Exercise 7.1](ex7_1.md) | [Exercise 7.3](ex7_3.md) \]
|
|
|
|
# Exercise 7.2
|
|
|
|
*Objectives:*
|
|
|
|
- Decorator chaining
|
|
- Defining decorators that accept arguments.
|
|
|
|
*Files Modified:* `logcall.py`, `validate.py`
|
|
|
|
## (a) Copying Metadata
|
|
|
|
When a function gets wrapped by a decorator, you often lose
|
|
information about the name of the function, documentation strings, and
|
|
other details. Verify this:
|
|
|
|
```python
|
|
>>> @logged
|
|
def add(x,y):
|
|
'Adds two things'
|
|
return x+y
|
|
|
|
>>> add
|
|
<function wrapper at 0x4439b0>
|
|
>>> help(add)
|
|
... look at the output ...
|
|
>>>
|
|
```
|
|
|
|
Fix the definition of the `logged` decorator so that it copies
|
|
function metadata properly. To do this, use the `@wraps(func)`
|
|
decorator as shown in the notes.
|
|
|
|
After you're done, make sure the decorator preserves the function name
|
|
and doc string.
|
|
|
|
```python
|
|
>>> @logged
|
|
def add(x,y):
|
|
'Adds two things'
|
|
return x+y
|
|
|
|
>>> add
|
|
<function add at 0x4439b0>
|
|
>>> add.__doc__
|
|
'Adds two things'
|
|
>>>
|
|
```
|
|
|
|
Fix the `@validated` decorator you wrote earlier so that it also preserves
|
|
metadata using `@wraps(func)`.
|
|
|
|
## (b) Your first decorator with arguments
|
|
|
|
The `@logged` decorator you defined earlier always just
|
|
prints a simple message with the function name.
|
|
Suppose that you wanted the user to be able to specify a
|
|
custom message of some sort.
|
|
|
|
Define a new decorator `@logformat(fmt)` that accepts
|
|
a format string as an argument and uses `fmt.format(func=func)` to
|
|
format a supplied function into a log message:
|
|
|
|
```python
|
|
# sample.py
|
|
...
|
|
from logcall import logformat
|
|
|
|
@logformat('{func.__code__.co_filename}:{func.__name__}')
|
|
def mul(x,y):
|
|
return x*y
|
|
```
|
|
|
|
To do this, you need to define a decorator that takes an argument.
|
|
This is what it should look like when you test it:
|
|
|
|
```python
|
|
>>> import sample
|
|
Adding logging to add
|
|
Adding logging to sub
|
|
Adding logging to mul
|
|
>>> sample.add(2,3)
|
|
Calling add
|
|
5
|
|
>>> sample.mul(2,3)
|
|
sample.py:mul
|
|
6
|
|
>>>
|
|
```
|
|
|
|
To further simplify the code, show how you can define the original `@logged` decorator
|
|
using the the `@logformat` decorator.
|
|
|
|
## (c) Multiple decorators and methods
|
|
|
|
Things can get a bit dicey when decorators are applied to methods in a
|
|
class. Try applying your `@logged` decorator to the methods in the
|
|
following class.
|
|
|
|
```python
|
|
class Spam:
|
|
@logged
|
|
def instance_method(self):
|
|
pass
|
|
|
|
@logged
|
|
@classmethod
|
|
def class_method(cls):
|
|
pass
|
|
|
|
@logged
|
|
@staticmethod
|
|
def static_method():
|
|
pass
|
|
|
|
@logged
|
|
@property
|
|
def property_method(self):
|
|
pass
|
|
```
|
|
|
|
Does it even work at all? (hint: no). Is there any way to fix the code so
|
|
that it works? For example, can you make it so the following example
|
|
works?
|
|
|
|
```python
|
|
>>> s = Spam()
|
|
>>> s.instance_method()
|
|
instance_method
|
|
>>> Spam.class_method()
|
|
class_method
|
|
>>> Spam.static_method()
|
|
static_method
|
|
>>> s.property_method
|
|
property_method
|
|
>>>
|
|
```
|
|
|
|
## (d) Validation (Redux)
|
|
|
|
In the last exercise, you wrote a `@validated` decorator that enforced
|
|
type annotations. For example:
|
|
|
|
```python
|
|
@validated
|
|
def add(x: Integer, y:Integer) -> Integer:
|
|
return x + y
|
|
```
|
|
|
|
Make a new decorator `@enforce()` that enforces types specified
|
|
via keyword arguments to the decorator instead. For example:
|
|
|
|
```python
|
|
@enforce(x=Integer, y=Integer, return_=Integer)
|
|
def add(x, y):
|
|
return x + y
|
|
```
|
|
|
|
The resulting behavior of the decorated function should be identical.
|
|
Note: Make the `return_` keyword specify the return type. `return` is
|
|
a Python reserved word so you have to pick a slightly different name.
|
|
|
|
|
|
**Discussion**
|
|
|
|
Writing robust decorators is often a lot harder than it looks.
|
|
Recommended reading: [How you implemented your Python decorator is wrong](https://github.com/GrahamDumpleton/wrapt/blob/develop/blog/01-how-you-implemented-your-python-decorator-is-wrong.md)
|
|
|
|
\[ [Solution](soln7_2.md) | [Index](index.md) | [Exercise 7.1](ex7_1.md) | [Exercise 7.3](ex7_3.md) \]
|
|
|
|
----
|
|
`>>>` Advanced Python Mastery
|
|
`...` A course by [dabeaz](https://www.dabeaz.com)
|
|
`...` Copyright 2007-2023
|
|
|
|
. This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/)
|