python decorator

we first start with python closure and then get an understanding with
python decorator.

def outer():
    x = 1
    def inner():
        print("value of x is:", x)
    return inner

func = outer()

func()

Everything works according to Python’s scoping rules - x is a local variable
in our function outer. When inner prints x, Python looks for a local variable
to inner and not finding it looks in the enclosing scope which is the function
outer, finding it there.

But what about things from the point of view of variable lifetime? Our variable
x is local to the function outer which means it only exists while the function
outer is running. We aren’t able to call inner till after the return of outer
so according to our model of how Python works, x shouldn’t exist anymore by
the time we call inner and perhaps a runtime error of some kind should occur.

It turns out that, against our expectations, our returned inner function does
work. Python supports a feature called function closures which means that
inner functions defined in non-global scope remember what their enclosing
namespaces looked like at definition time.
This can be seen by looking at the
func.__closure__ attribute of our inner function which contains the
variables in the enclosing scopes.

Remember - the function inner is being newly defined each time the function
outer is called. Right now the value of x doesn’t change so each inner function
we get back does the same thing as another inner function - but what if we
tweaked it a little bit?

def outer(x):
    def inner():
        print(x)
    return inner

func1 = outer(1)
func1()

func2 = outer(2)
func2()

From this example you can see that closures - the fact that functions remember
their enclosing scope - can be used to build custom functions that have,
essentially, a hard coded argument. We aren’t passing the numbers 1 or 2 to our
inner function but are building custom versions of our inner function that
“remembers” what number it should print.

This alone is a powerful technique - you might even think of it as similar to
object oriented techniques in some ways: outer is a constructor for inner with
x acting like a private member variable. And the uses are numerous - if you are
familiar with the key parameter in Python’s sorted function you have probably
written a lambda function to sort a list of lists by the second item instead of
the first. You might now be able to write an itemgetter function that accepts
the index to retrieve and returns a function that could suitably be passed to
the key parameter.

now get start with the python decorator:

A decorator is just a callable that takes a function as an argument and
returns a replacement function. We’ll start simply and work our way up to
useful decorators.

def outer(some_func):
    def inner():
        print('before some func')
        result = some_func()
        return result + 1
    return inner

def func1():
    return 1

func2 = outer(func1)

func2()

Look carefully through our decorator example. We defined a function named outer
that has a single parameter some_func. Inside outer we define an nested function
named inner. The inner function will print a string then call some_func, catching
its return value.

The value of some_func might be different each time outer is called, but whatever
function it is we’ll call it. Finally inner returns the return value of
some_func() + 1 - and we can see that when we call our returned function stored
in decorated. we get the results of the print and also a return value of 2 instead
of the original return value 1 we would expect to get by calling func1.

we could say the func2 is a decorated version of func1. it’s func1 plus some thing.

import time


def get_run_time(some_function):

    def wrapper():
        start = time.time()
        some_function()
        end = time.time()
        return "the function has took : " + str((end - start)) + 'n'
    return wrapper

@get_run_time
def run_test():
    for index in range(1000):
        pass

print(run_test())

this may be helpful to test the run time of a function.

the decorator can be more generic when work with the *args and **kwargs.

from functools import wraps
from flask import g, request, redirect, url_for


def login_required(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if g.user is None:
            return redirect(url_for('login', next=request.url))
        return f(*args, **kwargs)
    return decorated_function


@app.route('/secret')
@login_required
def secret():
    pass

this code comes from the flask source code. from here we also can know that the
decorator work from most inner to outer.

happy coding.