Look at the following rather not-so-uncommon scenario in programming.
You have a number of different functions in your program – maybe 10 or 20 or more of them. Each of the functions is doing something entirely different, but generically looks like the following Python example:
def function1(): #do something (multiple statements)
Now, supposing we want to execute the line “do something” only when a certain constant condition is met. For example, we want to do something only when the user is an Admin.
Then for each of the several functions, we would have to add the condition:
def function1(): if user_is_admin: #do something
So far, so easy and straightforward.
Suppose, further, that user_is_admin is not just a one-liner; that it is a complex multi-line condition. Then we will have to add the complex multi-lines to each of the 20 functions!
What if at some point the logic of user_is_admin changes? Then we have to change the logic individually in each of the 20 functions!
What if instead of just 20 functions, we instead have 50 or more such functions? Then life just gets more complicated.
Welcome Decorators in Python
With the concept of Decorators in Python, we are able to resolve our nightmare described in the preceding section, but first, let’s understand Decorators.
Decorators dynamically alter the functionality of a function, method, or class without having to directly use subclasses or change the source code of the function being decorated. Using decorators in Python also enforces DRY (Don't Repeat Yourself) code.
If you use Flask, the first Decorator you probably already used (maybe without knowing) is for defining the routes, like below. Note that the symbol "@" denotes Decorator.
@app.route
To get some more context about Decorators, we are always told that Functions in Python are first class objects, which means they can be assigned to a variable, passed as an argument, or returned from another function and stored in any data structure. This is the basis of Decorators.
The first class object property of a function helps us to use the concept of Decorators in Python. Decorators are functions which take another function as an argument, and enables us to put useful logic or functionality either at the start or end of the execution of the argument function.
How to define Decorator
I will now give a practical example of how I define and use a Decorator in my applications
Define a Decorator called admin_only
def admin_only(func):
"""
admin_only is the decorator function; func is the function being decorated
"""
@wraps(func) # very important
def wrapper(*args, **kwargs):
# do something - this is the common action for decorator
# the code below is the equivalence of "if user_is_admin"
roles = [r.record_name for r in current_user.roles]
if not ('Admin' in roles or session.get('temp_admin')):
message_client = 'Access denied - this is for Admin only'
# my custom heaader 'X-Source-From-Fetch' - see fetch calls
ajax_call = request.headers.get('X-Requested-With') or request.headers.get('X-Source-From-Fetch')
if ajax_call: # if endpoint returns json ##not being used currently
result = {'AccessDeniedMessage': message_client}
escape_exclusions = ['aHtml']
# escape only strings #serialize the data in the form of json, and set the Content-Type of the response to application/json
return jsonify({k: v if (not isinstance(v, str) or k in escape_exclusions) else escape((v)) for k, v in result.items()})
else: # if endpoint returns render_template
flash(message_client)
return render_template('tableCommonDiv.html', whichList='Common', status='Active', common_message='Access denied')
# then execute the function being decorated
return func(*args, **kwargs)
return wrapper
From the above, you can see that the general format of a Decorator definition is:
def my_decorator_before(func):
@wraps(func) # very important
def wrapper(*args, **kwargs):
# do something here - this is the common action for decorator
# then execute the function being decorated
return func(*args, **kwargs)
return wrapper
or:
def my_decorator_after(func):
@wraps(func) # very important
def wrapper(*args, **kwargs):
# execute the function being decorated
return func(*args, **kwargs)
# then do something here - this is the common action for decorator
return wrapper
How to use a Decorator
Now, use a Decorator called admin_only to decorate different functions by prepending the @ symbol.
@admin_only #decorate the function function1
def function1(): #do something
@admin_only #decorate the function function2
def function2(): #do something
@admin_only #decorate the function function3
def function3(): #do something
Above, please note that the symbol "@" denotes Decorator.
Also note that I no longer have the condition "if user_is_admin" in the three code snippets above since the Decorator takes care of that.
No comments:
Post a Comment