Python is a beautiful language. One of the multiple reasons, what makes it powerful is the fact that functions in python are first class objects. That means anywhere you can pass any other object, you can pass functions, too. This is really powerful mechanism than it appears. Wait, does that mean you can actually return function as an object from another function? Of course, you can. Let's look at an example.
def Message():
return "Hello world!"
def Greeting():
return Message
print(Greeting())
Result:
<function Message at 0x02DFA780>
See that? But that is not what we intended. To fix the issue we can fix print statement above and it will do what we wanted it to do:
def Message():
return "Hello world!"
def Greeting():
return Message
print(Greeting()())
Result:
Hello world!
But can function returned by Greeting enclosed or contained within that function? Yes, it can be, and that is the basis of closures.
def Greeting():
def Message():
return "Hello world!"
return Message
print(Greeting()())
Result:
Hello world!
This mechanism is called lexicographic closure. Essentially you can define functions within a function, return child function by setting some context from its parent function and use new “decorated” function to call the function. Here’s another example:
def multiply_by(n):
def multiply(x):
return n*x
return multiply
multiply_by_2 = multiply_by(2)
print(multiply_by_2(10))
multiply_by_3 = multiply_by(3)
print(multiply_by_3(10))
Result:
20
30
In above example, the value of ‘n’ is input to ‘multiply_by’ function, which is stored in the context of ‘multiply’ function, when it gets returned to the caller. This, though may appear simple, it is very powerful technique. This forms basis for another useful application of closures – decorators.
Let's take an example:
def DivideByZeroCheckDecorator(func):
def DivideByZeroCheck(a, b):
if b == 0:
return "infinite, as denomitor is zero"
else:
return func(a, b)
return DivideByZeroCheck
@DivideByZeroCheckDecorator
def DivideFunc(a, b):
return (a/b)
print("3/3 is ", DivideFunc(3, 3))
print("3/1 is ", DivideFunc(3, 1))
print("3/0 is ", DivideFunc(3, 0))
Result:
3/3 is 1.0
3/1 is 3.0
3/0 is infinite, as denomitor is zero
Now, what just happened here? We called ‘DivideFunc’ which has some kind of ‘@<functionname>’ syntax on top of it, which is essentially a function using the closure mechanism. What is happening here is function hooked with ‘@<functionname>’ syntax gets called with ‘DivideFunc’ function object as input, which in turn then returns new form function object and that takes same arguments input as ‘DivideFunc’. So every time we call ‘DivideFunc’, the actual function executing are in this order:
DivideByZeroCheckDecorator ->DivideByZeroCheck -> DivideFunc ->print
Pretty cool, isn’t it? This mechanism is called “decorators” in python. Essentially, our original function ‘DivideFunc’ got “decorated” with ‘DivideByZeroCheck’ function object. If you like me and come from a C++ background, you can easily relate that decorator pattern in C++ tries to achieve similar results, the only difference is python is much simpler in achieving the end result as functions are first class objects in python. ‘@’ syntax is just syntactic sugar by python, mere convenience, you can also pass your functions directly to function decorating other function objects.
So when do you use decorators? If you start thinking about how you can write decorators, which are reusable and handy, applications are endless. Here are some common scenarios where you can employ them:
Pretty printing results - decorating function can do anything before or after function call to the decorated function
Authentication/authorization - If you are using Django like web frameworks where you use python backend, you can write decorators for authentication/authorization
Smart checks - like divide by zero check decorator above, you can actually build a library of common checks you always do in your application and keep them handy for reuse by everyone else in your team
Argument un/packing – If your application uses network transfer and server-client protocol, then decorators for un/packing parameters from/to network payloads definitely will go long way in your application maintenance
Etc.
This in no means an all-inclusive list, it is something just to get you started and get going to think in terms of reusability and start writing useful and handy decorators.
Conclusion:
Enclosures and decorators are very powerful beasts in python. When used wisely, they allow you to create reusable pieces of code and allow them to be hooked up on-the-fly to any new or existing functions to do more useful things, which otherwise would mean writing repetitive code. They not only reduce and simply repetitive code, they also self-document the purpose of additional function calls which do “decoration” to existing functionality.
Comments