157 lines
3.4 KiB
Markdown
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)
|