3.4 KiB
3.4 KiB
Exercise 7.2 - Solution
(a) Copying Metadata
# 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
# 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:
= logformat('Calling {func.__name__}') logged
(c) Decorators and methods
You can get the code to work if you interchange the order of the decorators. For example:
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)
# validate.py
...
from inspect import signature
from functools import wraps
def validated(func):
= signature(func)
sig
# Gather the function annotations
= dict(func.__annotations__)
annotations
# Get the return annotation (if any)
= annotations.pop('return', None)
retcheck
@wraps(func)
def wrapper(*args, **kwargs):
= sig.bind(*args, **kwargs)
bound = []
errors
# Enforce argument checks
for name, validator in annotations.items():
try:
validator.check(bound.arguments[name])except Exception as e:
f' {name}: {e}')
errors.append(
if errors:
raise TypeError('Bad Arguments\n' + '\n'.join(errors))
= func(*args, **kwargs)
result
# 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):
= annotations.pop('return_', None)
retcheck
def decorate(func):
= signature(func)
sig
@wraps(func)
def wrapper(*args, **kwargs):
= sig.bind(*args, **kwargs)
bound = []
errors
# Enforce argument checks
for name, validator in annotations.items():
try:
validator.check(bound.arguments[name])except Exception as e:
f' {name}: {e}')
errors.append(
if errors:
raise TypeError('Bad Arguments\n' + '\n'.join(errors))
= func(*args, **kwargs)
result
if retcheck:
try:
retcheck.check(result)except Exception as e:
raise TypeError(f'Bad return: {e}') from None
return result
return wrapper
return decorate