Create an account


Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[Tut] Closures and Decorators in Python

#1
Closures and Decorators in Python

<div><p>This tutorial teaches you two advanced Python skills: closures and decorators. Mastering them will make you a better coder today—so, let’s dive right into them!</p>
<h2>Closures</h2>
<p>Every function in Python is first class, because they can be passed around like any other object. Usually, when a programming language creates a function just like other data types, that programming language supports something called Closures.</p>
<p>A closure is a nested function. It is defined within an outer function.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">def outer_hello_fn(): def hello(): print("Hello Finxter!") hello()
</pre>
<p>Here, we have an outer function called <code>outer_ hello_ fn</code>, it has no input arguments. The function <code>hello</code> is a nested function defined within the outer function. The <code>hello</code> function is a closure.</p>
<p><strong>Try It Yourself:</strong></p>
<p> <iframe src="https://trinket.io/embed/python/c24797587b" width="100%" height="356" frameborder="0" marginwidth="0" marginheight="0" allowfullscreen></iframe> </p>
<p><em><strong>Exercise</strong>: What’s the output of this code snippet? Run the code to test if you’re correct. </em></p>
<p>When the outer function is called, the <code>hello</code> function within it will be defined and then invoked. Here is the function call and output:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">outer_hello_fn()</pre>
<p>Output:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">Hello Finxter!</pre>
<p>hello has been defined within <code>outer_hello_fn</code>, which means if you try and invoke the <code>hello</code> function, it will not work.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">hello()</pre>
<p>Output:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">NameError: name 'hello' is not defined</pre>
<p>If you want access to a function that is defined within another function, return the function object itself. Here is how.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">def get_hello_fn(): def hello(): print("Hello Finxter!") return hello
</pre>
<p>The outer function is called <code>get_hello_fn</code>. <code>hello</code>, is an inner function, or closure. Instead of invoking this hello function, simply return the <code>hello</code> function to whoever calls <code>get_hello_fn</code>. For example:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">hello_fn = get_hello_fn()</pre>
<p>Invoking <code>get_hello_fn</code> stores the return function object in the <code>hello_fn </code>variable. If you explore the contents of this <code>hello_fn </code>variable, you will see that it is a function object.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">hello_fn</pre>
<p>Output:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">&lt;function __main__.get_hello_fn.&lt;locals>.hello></pre>
<p>As you can see in the structure, it is a locally defined function within <code>get_hello_fn</code>, that is, a function defined within another function, that is a closure. Now, this closure can be invoked by using the hello_fn variable.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">hello_fn()</pre>
<p>Output:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">Hello Finxter!</pre>
<p>Invoke <code>hello_fn()</code> will print out <code>Hello Finxter!</code> to screen. A closure is something more than just an inner function defined within an outer function. There is more to it. Here is another example:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">def hello_by_name(name): def hello(): print("Hello!", name) hello() return hello
</pre>
<p>Here, the outer function is called <code>hello_by_name</code>, which takes in one input argument, the name of an individual. Within this outer function, there is the <code>hello</code> inner function. It prints to the screen <code>Hello!</code>, and the value of the name.</p>
<p>The name variable is an input argument to the outer function. It is also accessible within the inner hello function. The name variable here can be thought of as a variable that is local to the outer function. Local variables in the outer function can be accessed by closures. Here is an example of passing an argument to the outside function:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">greet_hello_fn = hello_by_name("Chris")</pre>
<p>The function hello is returned and it is stored in the <code>greet_hello_fn </code>variable.</p>
<p>Executing this prints out <code>Hello! Chris</code> to screen. That is because we invoked the closure from within the outer function. We have a reference to the closure that was defined by the outer function.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">greet_hello_fn()</pre>
<p>Output:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">Hello! Chris</pre>
<p>Notice something interesting here. Chris is available in the variable name which is local to the <code>hello_by_name </code>function.</p>
<p>Now, we have already invoked and exited <code>hello_by_name </code>but the value in the name variable is still available to our closure. And this is another important concept about closures in Python. They hold the reference to the local state even after the outer function that has defined the local state has executed and no longer exists. Here is another slightly different example illustrating this concept.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">def greet_by_name(name): greeting_msg = "Hi there!" def greeting(): print(greeting_msg, name) return greeting
</pre>
<p>The outer function, <code>greet_by_name</code>, takes in one input argument, name. Within the outer function, a local variable called <code>greeting_msg </code>is defined which says, <code>“Hi there!”</code>. A closure called greeting is defined within the outer function. It accesses the local variable <code>greeting_msg </code>as well as the input argument name. A reference to this greeting closure is returned from the outer <code>greet_by_name </code>function.</p>
<p>Let’s go ahead and invoke greet_by_name and store the function object that it returns in the greet_fn variable. We will use this function object to greet Ray by name. Go ahead and invoke the greet_fn() by specifying parentheses. And it should say, Hi there! Ray. Observe how the closure has access not just to the name Ray but also to the greeting message, even after we have executed and exited the outer function.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">greet_fn = greet_by_name("Ray")
greet_fn()
</pre>
<p>Output:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">Hi there! Ray</pre>
<p>Closures carry around information about the local state. Let’s see what happens when the greet_by_name function is deleted,&nbsp;so you no longer have access to the outer function.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">del greet_by_name</pre>
<p>Now, remember that name and greeting message are both variables that were defined in the outer function. What happens to them? Now if you try to invoke greet by name.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">greet_by_name("Ray")</pre>
<p>Output:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">NameError: name 'greet_by_name' is not defined</pre>
<p>What about the greet_fn?</p>
<p>Remember that greet_fn is a reference to our closure. Does this still work?</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">greet_fn()</pre>
<p>Output:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">Hi there! Ray</pre>
<p>Not only does it work, but it still has access to the local variables that were defined in the outer function. The outer function no longer exists in Python memory, but the local variables are still available along with our closure.</p>
<h2>Decorators – Code Modification</h2>
<p>Decorators help to add functionality to existing code without having to modify the code itself. Decorators are so-called because they decorate code, they do not modify the code, but they make the code do different things using decoration. Now that we have understood closures, we can work our way step by step to understanding and using decorators.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">def print_message(): print("Decorators are cool!")
</pre>
<p>Here is a simple function that prints a message to screen.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">print_message()</pre>
<p>Output:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">Decorators are cool!</pre>
<p>Each time you invoke this function it will always print the same message. I want to use a few characters to decorate the original message, and I do this using the highlight function.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import random def highlight(): annotations = ['-', '*', '+', ':', '^'] annotate = random.choice(annotations) print(annotate * 50) print_message() print(annotate * 50)
</pre>
<p>The outer function highlight has no input arguments. Within the highlight function, a random choice of annotations is used to decorate the original message. The message will be highlighted with a random choice between the dash, the asterisk, the plus, the colon, and the caret.  The output will have an annotation of 50 characters before and after the message which is inside the print_message function.</p>
<p><strong>Try It Yourself:</strong></p>
<p> <iframe src="https://trinket.io/embed/python/c616622162" width="100%" height="356" frameborder="0" marginwidth="0" marginheight="0" allowfullscreen></iframe> </p>
<p><em><strong>Exercise</strong>: What’s the output of this code snippet? Run the code to test your understanding!</em></p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">highlight()</pre>
<p>Output:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">::::::::::::::::::::::::::::::::::::::::::::::::::
Decorators are cool!
::::::::::::::::::::::::::::::::::::::::::::::::::
</pre>
<p>Here is another function with a different message, print_another_message.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">def print_another_message(): print("Decorators use closures.")
</pre>
<p>Now if I want to highlight this message as well, the existing highlight function will not work because it has been hardcoded to invoke the print_message function. So how do I change this highlight function so that it is capable of highlighting any message that I want printed out to screen? Remember that functions are first-class citizens in Python, which means whatever print function you have, you can pass it as an input argument to the highlight function. Here is a redefined highlight function, make_highlighted.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">def make_highlighted(func): annotations = ['-', '*', '+', ':', '^'] annotate = random.choice(annotations) def highlight(): print(annotate * 50) func() print(annotate * 50) return highlight
</pre>
<p>The only difference here is that make_highlighted takes in an input argument that is a function. This function is what will print out the message to be displayed. The next change is that within the highlight closure, the function object that was passed in is invoked. That is the function object that will print out the message. Now we have two print functions so far.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">print_message()
print_another_message()
</pre>
<p>And now with the help of  make_highlighted function, any printed message can be highlighted. For example:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">highlight_and_print_message = make_highlighted(print_message) highlight_and_print_message()
</pre>
<p>Output:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">++++++++++++++++++++++++++++++++++++++++++++++++++
Decorators are cool!
++++++++++++++++++++++++++++++++++++++++++++++++++
</pre>
<p>To print a different message and have it highlighted, simply pass a different function object to the make_highlighted function.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">highlight_and_print_another_message = make_highlighted(print_another_message) highlight_and_print_another_message()
</pre>
<p>Output:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Decorators use closures.
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
</pre>
<p>It is clear that the make_highlighted function is very generic, you can use it to highlight any message that you want printed to screen. The function make_highlighted is a decorator.</p>
<p>Why is it a decorator? Well, it takes in a function object and decorates it and changes it. In this example, it highlights the function with random characters. Decorators are a standard design pattern, and in Python, you can use decorators more easily. Instead of passing in a function object to make_highlighted, accessing the closure, and then invoking the closure, you can simply decorate any function by using @ and placing the decorator before the function to decorate.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@make_highlighted
def print_a_third_message(): print("This is how decorators are used")
</pre>
<p>The use of the decorator @make_highlighted will automatically pass the function print_a_third_message as an input to make_highlighted and highlight the message.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">print_a_third_message()</pre>
<p>Output:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This is how decorators are used
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
</pre>
<p>Now you can use the decorator to highlight any messages.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@make_highlighted
def print_any_message(): print("This message is highlighted!")
</pre>
<p>And now if you invoke print_any_message, you will find that the result that is displayed to screen is highlighted.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">print_any_message()</pre>
<p>Output: </p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">++++++++++++++++++++++++++++++++++++++++++++++++++
This message is highlighted!
++++++++++++++++++++++++++++++++++++++++++++++++++
</pre>
<h2>Decorators – Customization</h2>
<p>Let’s see another example of a Decorator that will do some work. It will do some error checking for us.</p>
<p>Here are two functions that will be the input to our decorator</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">def square_area(length): return length**2 def square_perimeter(length): return 4 * length
</pre>
<p>We assume that the value of the radius passed in is positive and correct.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">square_area(5)</pre>
<p>Output:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">25</pre>
<p>What if I invoke the square_area and pass in -1?</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">square_area(-1)</pre>
<p>Output:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">-4</pre>
<p>The input -1 doesn’t make sense as a value for the length. The function should have thrown an error or told us in some way that negative values of length are not valid. Now, if you were to perform an error check for each of these functions, we would have to do it individually. We would have to have an if statement within the area function as well as the perimeter function. Instead of that, let’s write a decorator that will perform this error checking for us. The decorator safe_calculate takes in one input argument that is a function object.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">def safe_calculate(func): def calculate(length): if length &lt;= 0: raise ValueError("Length cannot be negative or zero") return func(length) return calculate
</pre>
<p>This is the function object that will perform the calculation. Within the safe_calculate outer function, the inner function called calculate is the closure. calculate takes in one input argument, the length. It checks to see whether length is less than or equal to 0. If yes, it throws an error. And the way it throws an error is by simply calling a raise ValueError, “Length cannot be negative or zero”. Once we raise this error, Python will stop the execution. But if length is positive, it will invoke func and pass in length as an input argument. The safe_calculate is our decorator, which takes as its input a function object and returns a closure that will perform the safe calculation.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">square_area_safe = safe_calculate(square_area)</pre>
<p>Let’s test it first:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">square_area_safe(5)</pre>
<p>This is safe and I get the result here on the screen.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">25</pre>
<p>Invoking it with a negative number will raise an error</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">square_area_safe(-1)</pre>
<p>Output:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">ValueError: Length cannot be negative or zero</pre>
<p>Let’s decorate the perimeter function as well with the safe_calculate.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">square_perimeter_safe = safe_calculate(square_perimeter) square_perimeter(10)
</pre>
<p>Output:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">40</pre>
<p>But if you were to call square_perimeter_safe with a negative value for length well, that is a ValueError.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">square_perimeter_safe(-10)</pre>
<p>Output:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">ValueError: Length cannot be negative or zero</pre>
<p>Now that you have a decorator, you should decorate your functions rather than use the way that we have been using so far.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@safe_calculate
def square_area(length): return length**2 @safe_calculate
def square_perimeter(length): return 4 * length
</pre>
<p>Now, the next time square_area or the square_perimeter is called, the safety check will be performed.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">square_perimeter(3)</pre>
<p>Output:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">12</pre>
<p>If you try to calculate the perimeter for a negative value of the length, you will get a ValueError. The safe_calculate function that we set up earlier has a limitation, and&nbsp;you will see what it in a future example.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">square_perimeter(-3)</pre>
<p>Output:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">ValueError: Length cannot be negative or zero</pre>
<p>What happens when you have more than one input? Here is a function that calculates the area of a rectangle.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@safe_calculate
def rectangle_area(length, width): return length * width
</pre>
<p>Within our safe_calculate function, we had invoked the func object which performs the calculation with just one input argument, with just the variable length. This is going to cause a problem when we use the safe_calculate decorator for the rectangle_area function.</p>
<p>Once I have decorated this function, I’m going to invoke it with 4, 5.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">rectangle_area(4, 5)</pre>
<p>Output:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">TypeError: calculate() takes 1 positional argument but 2 were given</pre>
<p>The problem is with the way we had defined the closure inside the safe_calculate function.</p>
<p>The calculate closure takes in just one input argument. If a function has multiple input arguments, then safe_calculate cannot be used. A redefined safe_calculate_all function is shown below:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">def safe_calculate_all(func): def calculate(*args): for arg in args: if arg &lt;= 0: raise ValueError("Argument cannot be negative or zero") return func(*args) return calculate. </pre>
<p>It takes in one input argument that is the function object that is to be decorated. The main change is in the input arguments that are passed into the calculate closure. The function calculate now takes in variable length arguments, *args.  The function iterates over all of the arguments that were passed in, and checks to see whether the argument is less than or equal to 0. If any of the arguments are less than or equal to 0, a ValueError will be raised. Remember, *args will unpack the original arguments so that the elements of the tuple are passed in individually to the function object, func. You can now use this safe_calculate_all decorator with functions that have any number of arguments.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@safe_calculate_all
def rectangle_area(length, width): return length * width
rectangle_area(10, 3)
</pre>
<p>Output:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">30</pre>
<p>Let’s try invoking the same function, but this time one of the arguments is negative. Width is negative and&nbsp;that gives me a ValueError, thanks to our safe_calculate_all decorator.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">rectangle_area(10, -3)</pre>
<p>When you invoke this function, it will check all arguments.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">ValueError: Argument cannot be negative or zero</pre>
<p>It doesn’t matter which argument is negative, you still get the ValueError. Here the length is negative:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">rectangle_area(-10, 3)</pre>
<p>Output:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">ValueError: Argument cannot be negative or zero</pre>
<h2>Chaining Decorators</h2>
<p>You can have a function decorated using multiple decorators. And these decorators will be chained together.</p>
<p>Here are two decorators, one prints asterisks and the other plus signs</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">def asterisk_highlight(func): def highlight(): print("*" * 50) func() print("*" * 50) return highlight def plus_highlight(func): def highlight(): print("+" * 50) func() print("+" * 50) return highlight
</pre>
<p>The print_message_one is decorated with the asterisk_highlight.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@asterisk_highlight
def print_message_one(): print("Decorators are cool!") print_message_one()
</pre>
<p>Output:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">**************************************************
Decorators are cool!
**************************************************
</pre>
<p>Now let’s define another print function, but this time we will decorate it using two decorators, the plus_highlight and the asterisk_highlight.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@plus_highlight
@asterisk_highlight
def print_message_one(): print("Decorators are cool!")
</pre>
<p>What you see here is an example of chaining decorators together. But how are they chained? Which decoration comes first, the asterisk_highlight, or the plus_highlight? Whichever decorator is the closest to the function definition is what is executed first, and then the decorator which is further away from the function definition. This means that the message will be first highlighted with the asterisk, then the plus.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">print_message_one()</pre>
<p>Output:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">++++++++++++++++++++++++++++++++++++++++++++++++++
**************************************************
Decorators are cool!
**************************************************
++++++++++++++++++++++++++++++++++++++++++++++++++
</pre>
<p>If you change the order of the decorators, the decorations order will change as well.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@asterisk_highlight
@plus_highlight
def print_message_one(): print("Decorators are cool!") </pre>
<p>You will have the same function print_message_one, but the decorator that is closest to the function definition is the plus_highlight and then the asterisk_highlight.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">print_message_one()</pre>
<p>Output:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">**************************************************
++++++++++++++++++++++++++++++++++++++++++++++++++
Decorators are cool!
++++++++++++++++++++++++++++++++++++++++++++++++++
**************************************************
</pre>
<h2>Use of kwargs in Decorators</h2>
<p>In this example we are using kwargs to display different messages for a decorator that times the execution of a function</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">def timeit(func): def timed(*args, **kw): if 'start_timeit_desc' in kw: print(kw.get('start_timeit_desc')) ts = time.time() result = func(*args, **kw) te = time.time() if 'end_timeit_desc' in kw: print('Running time for {} is {} ms'.format(kw.get('end_timeit_desc'), (te - ts) * 1000)) return result return timed </pre>
<p>The timeit decorator is used for the test function.  Three parameters are passed to the function test: a, b and, **kwargs. The parameters a and b are handled in the decorator with *args as we have seen before.  The **kwargs parameter is used to pass descriptions for the function.  These parameters are start_timeit_desc and end_timeit_desc.  These two parameters are checked inside the timed closure and will display the messages that are in them.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@timeit
def test(a,b, **kwargs): return a * b result = test(10,20, start_timeit_desc = "Start of test(10,20)...", end_timeit_desc = "End of test(10,20)")
print("result of test(10,20) = " + str(result))
Output:
Start of test(10,20)...
Running time for End of test(10,20) is 0.0 ms
result of test(10,20) = 200
</pre>
</div>


https://www.sickgaming.net/blog/2020/08/...in-python/
Reply



Forum Jump:


Users browsing this thread:
1 Guest(s)

Forum software by © MyBB Theme © iAndrew 2016