4.0 KiB
[ Index | Exercise 7.1 | Exercise 7.3 ]
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:
>>> @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.
>>> @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:
# 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:
>>> import sample
Adding logging to add
Adding logging to sub
Adding logging to mul>>> sample.add(2,3)
Calling add5
>>> sample.mul(2,3)
sample.py:mul6
>>>
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.
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?
>>> 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:
@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:
@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:
[ Solution | Index | Exercise 7.1 | Exercise 7.3 ]
>>>
Advanced Python Mastery
...
A course by dabeaz
...
Copyright 2007-2023
.
This work is licensed under a Creative Commons
Attribution-ShareAlike 4.0 International License