python-mastery/Exercises/soln7_2.md
2023-07-16 20:21:00 -05:00

157 lines
3.4 KiB
Markdown

# Exercise 7.2 - Solution
## (a) Copying Metadata
```python
# logcall.py
from functools import wraps
def logged(func):
print('Adding logging to', func.__name__)
@wraps(func)
def wrapper(*args,**kwargs):
print('Calling', func.__name__)
return func(*args,**kwargs)
return wrapper
```
## (b) Decorators with arguments
```python
# logcall.py
from functools import wraps
...
def logformat(fmt):
def logged(func):
print('Adding logging to', func.__name__)
@wraps(func)
def wrapper(*args, **kwargs):
print(fmt.format(func=func))
return func(*args, **kwargs)
return wrapper
return logged
```
The earlier `@logged` decorator can be rewritten as follows:
```python
logged = logformat('Calling {func.__name__}')
```
## (c) Decorators and methods
You can get the code to work if you interchange the order of the
decorators. For example:
```python
from logcall import logged
class Spam:
@logged
def instance_method(self):
pass
@classmethod
@logged
def class_method(cls):
pass
@staticmethod
@logged
def static_method():
pass
@property
@logged
def property_method(self):
pass
```
Ponder why it doesn't work in the original order. Is there any way to make
the `@logged` decorator work regardless of the order in which its applied?
## (d) Validation (Redux)
```python
# validate.py
...
from inspect import signature
from functools import wraps
def validated(func):
sig = signature(func)
# Gather the function annotations
annotations = dict(func.__annotations__)
# Get the return annotation (if any)
retcheck = annotations.pop('return', None)
@wraps(func)
def wrapper(*args, **kwargs):
bound = sig.bind(*args, **kwargs)
errors = []
# Enforce argument checks
for name, validator in annotations.items():
try:
validator.check(bound.arguments[name])
except Exception as e:
errors.append(f' {name}: {e}')
if errors:
raise TypeError('Bad Arguments\n' + '\n'.join(errors))
result = func(*args, **kwargs)
# Enforce return check (if any)
if retcheck:
try:
retcheck.check(result)
except Exception as e:
raise TypeError(f'Bad return: {e}') from None
return result
return wrapper
def enforce(**annotations):
retcheck = annotations.pop('return_', None)
def decorate(func):
sig = signature(func)
@wraps(func)
def wrapper(*args, **kwargs):
bound = sig.bind(*args, **kwargs)
errors = []
# Enforce argument checks
for name, validator in annotations.items():
try:
validator.check(bound.arguments[name])
except Exception as e:
errors.append(f' {name}: {e}')
if errors:
raise TypeError('Bad Arguments\n' + '\n'.join(errors))
result = func(*args, **kwargs)
if retcheck:
try:
retcheck.check(result)
except Exception as e:
raise TypeError(f'Bad return: {e}') from None
return result
return wrapper
return decorate
```
[Back](ex7_2.md)