Decorators and Mixin Classes in
By: Sagar Chamling
What is Decorators?
A decorator is a design pattern in Python that allows to add new functionality to an existing object without modifying its structure.
def make_pretty(function):
def inner():
print("I got decorated")
function()
return inner
def ordinary():
print("I am ordinary")
ordinary = make_pretty(ordinary)
ordinary()
- Functions Inside other Functions
- Functions Returning other Functions
- Passing Functions as Arguments to other Functions
2019-02-06 00:20:32,851 - [ DEBUG ] Payload: ('1',) (decorators.py:10)
2019-02-06 00:20:32,851 - [ DEBUG ] Result: 1 (decorators.py:13)
2019-02-06 00:26:02,132 - [ ERROR ] ValueError: ("invalid literal for int() with base 10: '1.q'",)
(decorators.py:17)
2019-02-06 00:45:54,457 - [ ERROR ] ValueError: ("invalid literal for int() with base 10: '1.q'",)
(decorators.py:17)
2019-02-06 00:48:10,048 - [ ERROR ] ValueError: ("invalid literal for int() with base 10: '1.q'",)
(decorators.py:17)
2019-02-06 00:50:56,802 - [ ERROR ] ValueError: ("invalid literal for int() with base 10: '1.q'",)
(decorators.py:17)
2019-02-06 00:51:01,052 - [ ERROR ] ValueError: ("invalid literal for int() with base 10: '1.q'",)
(decorators.py:17)
2019-02-06 00:59:28,091 - [ ERROR ] ValueError: ("invalid literal for int() with base 10: '1.q'",)
(decorators.py:17)
2019-02-06 03:34:35,567 - [ ERROR ] ValueError: ("invalid literal for int() with base 10: '1.q'",)
(decorators.py:17)
Use Case
Logging
Debugging
But your code be like
def fetch_user(request):
data = request.json
try:
logger.info('Handling request %s', data['id'])
result = UserService.fetch(data['id'])
logger.debug('Return result: %s', result)
except NotFoundException ex:
logger.error('User not found: %s', result)
except Exception ex:
logger.error('Error while trying to fetch: %s', ex)
def delete_user(request):
data = request.json
try:
logger.info('Handling request %s', data['id'])
result = UserService.delete(data['id'])
logger.debug('Return result: %s', result)
except Exception ex:
logger.error('Error while trying to delete: %s', ex)
How @decorator helped?
def log(logger, prefix):
def decorator(function):
@wraps(function)
def wrapper(*args, **kwargs):
try:
logger.info('%s - Payload: %s %s'.format(prefix, args or '', kwargs or ''))
res = function(*args, **kwargs)
logger.debug('%s - Result: %s'.format(prefix=prefix, res=res))
return res
except Exception as ex:
logger.error('%s - %s: %s'.format(prefix, type(ex).__name__, ex.args))
raise ex # Call custom exception
return wrapper
return decorator
@log(logger, "FETCH_USER")
def fetch_user(request):
data = request.json
try:
logger.info('Handling request %s', data['id'])
result = UserService.fetch(data['id'])
logger.debug('Return result: %s', result)
except NotFoundException ex:
logger.error('User not found: %s', result)
except Exception ex:
logger.error('Error while trying to fetch: %s', ex)
def fetch(id):
user = User.fetch(id)
if not user:
raise NotFoundException('User not found.')
return user
This piece of code
def fetch_user(request):
data = request.json
try:
result = UserService.fetch(data['id'])
except NotFoundException ex:
return jsonify(message="User not found."), HTTPStatus.NOT_FOUND
except Exception:
return jsonify(message="Invalid Payload."), HTTPStatus.BAD_REQUEST
return jsonify(result=result)
@log(logger, 'FETCH_USER')
def fetch(id):
user = User.fetch(id)
if not user:
raise NotFoundException('User not found.')
return user
# logs
2019-02-06 03:34:44,282 [ INFO ] FETCH_USER - Payload: ('1234')
2019-02-06 03:34:44,282 [ DEBUG ] FETCH_USER - Result: {id: 1234, name: "Sagar Chamling"}
@api.route('/users/<string:user_id>', methods=['PUT'])
@authenticate(allow_all_users=True)
@validate_form(USER_PUT_SCHEMA)
def update_user(user_id):
...
@Decorator
- Compositional
- Modular
- Loosely Coupled
But wait
@validate(Type, Integer)
@transform(response_payload)
@log(logger, 'FETCH_USER')
def fetch(id):
user = User.fetch(id)
if not user:
raise NotFoundException('User not found.')
return user
- Validate Payload
- Transform its result
- Do Logging
If I want to
Can we avoid this decorator hell?
Yes
Mixin
It's special type of multiple inheritance
from jsonvalidate import (
NullContract,
EnumContract,
FloatContract,
RangeContract,
RegExContract,
LengthContract,
StringContract,
IntegerContract,
BooleanContract,
KeyMissingContract,
)
class String(KeyMissingContract, NullContract, StringContract, RegExContract, LengthContract, EnumContract):
pass
class Integer(KeyMissingContract, NullContract, IntegerContract, RangeContract, EnumContract):
pass
class Float(KeyMissingContract, NullContract, FloatContract, RangeContract, EnumContract):
pass
class Boolean(KeyMissingContract, NullContract, BooleanContract):
pass
It's superb right?
Understanding inheritance
class A:
def __init__(self):
print('Initialize A')
class B(A):
def __init__(self):
print('Initialize B')
class C(B):
def __init__(self):
print('Initialize C')
if __name__ == '__main__':
test = C()
Output?
Initialize C
Understanding inheritance
class A:
def __init__(self):
print('Initialize A')
class B(A):
def __init__(self):
A.__init__(self)
print('Initialize B')
class C(B):
def __init__(self):
B.__init__(self)
print('Initialize C')
if __name__ == '__main__':
test = C()
Output?
Initialize A
Initialize B
Initialize C
Understanding inheritance
class A:
def __init__(self):
print('Initialize A')
class B(A):
def __init__(self):
A.__init__(self)
print('Initialize B')
class C(A):
def __init__(self):
A.__init__(self)
print('Initialize C')
class D(B, C):
def __init__(self):
B.__init__(self)
C.__init__(self)
print('Initialize D')
if __name__ == '__main__':
test = D()
Output?
Initialize A
Initialize B
Initialize A
Initialize C
Initialize D
"deadly diamond of death"
class A:
def __init__(self):
print('Initialize A')
class B(A):
def __init__(self):
super().__init__()
print('Initialize B')
class C(A):
def __init__(self):
super().__init__()
print('Initialize C')
class D(B, C):
def __init__(self):
super().__init__()
print('Initialize D')
if __name__ == '__main__':
test = D()
Output?
Initialize A
Initialize C
Initialize B
Initialize D
Understanding inheritance super
C3 linearization a.k.a MRO
- Python creates a list of classes using C3
- Children precede their parents
- Kept in the order specified in the tuple of base classes
Understanding super MRO
class A:
def __init__(self):
print('Initialize A')
class B(A):
def __init__(self):
super().__init__()
print('Initialize B')
class C(A):
def __init__(self):
super().__init__()
print('Initialize C')
class D(B, C):
def __init__(self):
super().__init__()
print('Initialize D')
if __name__ == '__main__':
test = D()
Output
Initialize A
Initialize C
Initialize B
Initialize D
In [1]: D().__mro__
Out[2]: (<class '__main__.D'>, <class '__main__.B'>,
<class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
from jsonvalidate import (
NullContract,
EnumContract,
RangeContract,
RegExContract,
StringContract,
KeyMissingContract,
)
class String(KeyMissingContract, NullContract, StringContract, LengthContract,
EnumContract):
pass
Example from: https://github.com/RobusGauli/jsonvalidate
class NullContract(Contract):
def __init__(self, *args, **kwargs):
self.nullable = kwargs.get('nullable', False)
super().__init__(*args, **kwargs)
def check(self, val):
if not self.nullable and val is None:
return True, err(NullError())
return super().check(val)
USER_SCHEMA = Object({
'name': String(required=true, nullable=false)
}
It's easy to extend
Example from: https://github.com/RobusGauli/jsonvalidate
class RegExContract(Contract):
def __init__(self, *args, **kwargs):
self.regex = kwargs.get('regex', False)
super(RegExContract, self).__init__(*args, **kwargs)
def check(self, val):
if self.regex:
try:
regex = r"{}".format(self.regex)
if re.compile(regex) and not re.match(regex, val):
return True, err(RegExError())
except re.error as error:
raise ValueError('invalid regular expression')
return super(RegExContract, self).check(val)
USER_SCHEMA = Object({
'email': String(regex='^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$'),
}
class String(KeyMissingContract, NullContract, StringContract, RegExContract,
LengthContract, EnumContract):
"""Composition/Mixins for String"""
pass
Example from: https://github.com/RobusGauli/jsonvalidate
from jsonvalidate import String, Object, Integer, List
schema = Object({
'email': String(regex='[^@]+@[^@]+\.[^@]+'),
'name': String(max_length=3),
'age': Integer(range=[18, 25]),
'address': Object({
'permanent': String(),
'temporary': String(min_length=3, enums=['asss', 's'])
}),
'friends': List(Object({
'name': String(),
'nick_name': String()
}))
})
payload = {
'email': 'sgr.raee@gmail.com',
'name': 'r',
'age': 18,
'address': {'permanent': 'sd', 'temporary': 'asss'},
'friends': [{'name': 'robus', 'nick_name': 'sd'}]
}
if __name__ == '__main__':
print(schema.check(payload))
False, {
"email": null,
"name": null,
"age": null,
"address": {
"permanent": null,
"temporary": null
},
"friends": {
"0": {
"name": null,
"nick_name": null
}
}
}
Example from: https://github.com/RobusGauli/jsonvalidate
from jsonvalidate import String, Object, Integer, List
schema = Object({
'email': String(regex='[^@]+@[^@]+\.[^@]+'),
'name': String(max_length=3),
'age': Integer(range=[18, 25]),
'address': Object({
'permanent': String(),
'temporary': String(min_length=3, enums=['asss', 's'])
}),
'friends': List(Object({
'name': String(),
'nick_name': String()
}))
})
payload = {
'email': 'sgr.raee@gmail.com',
'name': 'r',
'age': 11,
'address': {'permanent': 'sd', 'temporary': 'asss'},
'friends': [{'name': 'robus', 'nick_name': 'sd'}]
}
if __name__ == '__main__':
print(schema.check(payload))
True {
"email": null,
"name": null,
"age": {
"range_error": {
"actual_val": 11,
"valid_range": [
18,
25
],
"type": "range_error"
}
},
"address": {
"permanent": null,
"temporary": null
},
"friends": {
"0": {
"name": null,
"nick_name": null
}
}
}
Takeaways
- Compositional Pattern improves Readability
- Use Decorator but Avoid Decorator Hell
- Use mixin only if you understand MRO
Thank You
Decorators and Mixin Classes in Python
By Sagar Chamling
Decorators and Mixin Classes in Python
This presenation is about the compositional pattern in Python using Decorators and Mixin Classes. It will focus on how we could utilize compositional pattern in python using ideas of higher order functions and mixin classes. It also goes beyond decorators and into the world of Mixin classes for the advanced compositional pattern by manipulating "method resolution order" in order to achieve the abstractions. Special Thanks to Robus Gauli.
- 357