Techno Blender
Digitally Yours.

Become Fluent in Python Decorators via Visualization | by Chengzhi Zhao | Jan, 2023

0 37


Photo by Huyen Bui on Unsplash

Python decorator is syntactic sugar. You can achieve everything without explicitly using the decorator. However, Using the decorator can help your code be more concise and readable. Ultimately, you write fewer lines of code by leveraging Python decorators.

Nevertheless, Python decorator isn’t a trivial concept to comprehend. Understanding Python decorators requires building blocks, including closure, function as an object, and deep knowledge of how Python code is executed.

Many online resources talk about Python decorators, but many tutorials only provide some sample code walkthroughs. Reading sample code could help to get a basic understanding of Python decorator. When it comes time to implement your decorator, we might still need clarification and clarity about the decorator concept and yet have to refer to the online resource to refresh our memory for detail.

Reading code sometimes doesn’t deepen memory but seeing images does. In this article, I want to help you understand Python decorator with some visualization and fun examples.

Python Functions are Objects | Image by Author

If we went to Python programming 101 class, we might get a drawing like this. We defined a variable, a representational name used to reference an object. The variable foo points to an object at a given time. It can be a list [1,2,3,4] , it can be a dictionary [“city”: “New York”], or it can a string “I like dumplings”

A less covered discussed topic in Python is that the variable foo can also point to a function add() . When the variable refers to a function, the foo can pass around the same way you used other types like int, str, list, or dict.

What does that mean I can pass foo around? This unlocks us to pass a function as an argument. Furthermore, we can return a function as a return type

# Python Functions are Objects
def I_am_1():
return 1

def return_1():
return I_am_1

def add(a, b):
return a + b

foo = add

del add
## let remove the original defintion

print(foo(return_1()(),2))
## ouput 3
print(foo(foo(return_1()(),2),3))
## output 6

Code walkthrough

  • Function Definition: we defined three functions called add which adds two objects; I_am_1() which simply returns number 1; return_1 which return the function I_am_1
  • Interesting Fact: Then we point to another variable foo to it. To prove that function is an object like the other types, we can even remove the original function reference named add . The rest of the code is still runnable due to foo refers to the object for the function.
  • Result Explain: return_1()() looks wired at first. If we take a closer look, it is the same way as we are calling the function, return_1() is I_am_1 as it just returns this function. In this case, we can think return_1()()=1mentally, so it does not surprise us the foo(1,2) gives an output of 3. We can also pass foo(1,2) to another function. In this case, we passed it to itself. Since foo(1,2)=3 , the outer function will operate as foo(3,3) . To get the result, we can pass the entire function with its arguments over and let the program execute at runtime to decipher everything.

Code Visualisation

Pass Function to another Function | Image By Author

A Python decorator transforms the functionality of the original object without modifying the original one. It is syntax sugar that allows users to extend the object’s ability more conveniently instead of duplicating some existing code. Decorators are a pythonic implementation of the decorator design pattern (Note: python decorator isn’t precisely the same as decorator design pattern).

We have discussed that we can return a function as a return type. Now, we extend that idea to demonstrate how a decorator works: we can return a function as a return type within another function.

To make our example more fun, we can create our decorator like a wrapper around a function and later stacks the multiple decorators.

Let’s first define our functions and use the example of making some dumplings. 🥟🥟🥟

## Python Decorators Basic -- I love dumplings
def add_filling_vegetable(func):
def wrapper(*args, **kwargs):
print("<^^^vegetable^^^>")
func(*args, **kwargs)
print("<^^^vegetable^^^>")
return wrapper

def add_dumplings_wrapper(func):
def wrapper(*args, **kwargs):
print("<---dumplings_wrapper--->")
func(*args, **kwargs)
print("<---dumplings_wrapper--->")
return wrapper

def dumplings(ingredients="meat"):
print(f"({ingredients})")

add_dumplings_wrapper(add_filling_vegetable(dumplings))()
# <---dumplings_wrapper--->
# <^^^vegetable^^^>
# (meat)
# <^^^vegetable^^^>
# <---dumplings_wrapper--->
add_dumplings_wrapper(add_filling_vegetable(dumplings))(ingredients='tofu')
# <---dumplings_wrapper--->
# <^^^vegetable^^^>
# (tofu)
# <^^^vegetable^^^>
# <---dumplings_wrapper--->

Code walkthrough

  • Function Definition: we defined three functions called add_filling_vegetable, add_dumplings_wrapper, and dumplings . Within add_filling_vegetable and add_dumplings_wrapper, we define a wrapper function that wraps around the original function passed as an argument. Additionally, we can do additional things. In this case, we print some text before and after the original function. If the original function also defined parameters, we can pass them in from the wrapper function. We also leverage the magic *args and **kwargs to be more flexible
  • Result Explain
  1. we can call the default ingredients for meat by using the default one add_dumplings_wrapper(add_filling_vegetable(dumplings))() , we can see the function are chaining together. It’s not readable, which we will fix shortly with decorator syntax. The core idea here is that we keep passing the function object as an argument to another. The function does two things: 1) continue doing the unmodified original function; 2) execute additional code. The execution of the entire code is like a round trip.
add_dumplings_wrapper(add_filling_vegetable(dumplings))() | Image By Author

2. for add_dumplings_wrapper(add_filling_vegetable(dumplings))(ingredients=’tofu’) it changes the default ingredients from meat to tofu by passing an additional argument. In this case, *args and **kwargs is very useful for resolving the complexity. The wrapper functions don’t have to unwrap to know the context of the argument. As a decorator, it is safe to pass down the actual function without modifying them.

add_dumplings_wrapper(add_filling_vegetable(dumplings))(ingredients=’tofu’)

So far, we haven’t touched the decorator syntax yet. Let’s make a small change in how we define the original function and call it fancy_dumplings.

## Stack decorator, the order matters here
@add_dumplings_wrapper
@add_filling_vegetable
def fancy_dumplings(ingredients="meat"):
print(f"({ingredients})")

fancy_dumplings()
# <---dumplings_wrapper--->
# <^^^vegetable^^^>
# (meat)
# <^^^vegetable^^^>
# <---dumplings_wrapper--->

fancy_dumplings(ingredients='tofu')
# <---dumplings_wrapper--->
# <^^^vegetable^^^>
# (tofu)
# <^^^vegetable^^^>
# <---dumplings_wrapper--->

Now decorator simplifies how we call all the functions and makes it more readable. We only need to call fancy_dumplings only once. it is much cleaner visually than nesting multiple functions horizontally.

Great! How to create the Python decorators are clear to us now. What can I use decorators for?

There are many potential practical use cases for Python decorators. It makes your code easier to read and dynamically. Note you don’t necessarily need to go decorators. We can always implement a wrapper around another function to achieve the same goal. The decorator is just syntactic sugar.

You can build your time decorator to evaluate the performance for a given function. The following is a timer example from Primer on Python Decorators

import functools
import time

def timer(func):
"""Print the runtime of the decorated function"""
@functools.wraps(func)
def wrapper_timer(*args, **kwargs):
start_time = time.perf_counter()
value = func(*args, **kwargs)
end_time = time.perf_counter()
run_time = end_time - start_time
print(f"Finished {func.__name__!r} in {run_time:.10f} secs")
return value
return wrapper_timer

It measures the time to execute a given function. Let’s stack it with our dumplings example.

@timer
@add_dumplings_wrapper
@add_filling_vegetable
def fancy_dumplings(ingredients="meat"):
print(f"({ingredients})")

fancy_dumplings()
# <---dumplings_wrapper--->
# <^^^vegetable^^^>
# (meat)
# <^^^vegetable^^^>
# <---dumplings_wrapper--->
# Finished 'wrapper' in 0.0000334990 secs

You can keep stacking the decorators to achieve your goal by simply calling the original function without worrying about wrapping other functions around when calling, as we have provided the sequence of the decorator when we define the original function.

Photo by Markus Spiske on Unsplash

There are many possibilities you can leverage the Python decorators. Essentially, it’s a wrapper to alert the behavior of the original function. It depends on your perspective to judge how practical the decorator is, but it shouldn’t be a syntax that feels foreign to you. I hope with some visualization, the concept of decorators becomes more straightforward to understand. Please let me know if you have any comments on this story.


Photo by Huyen Bui on Unsplash

Python decorator is syntactic sugar. You can achieve everything without explicitly using the decorator. However, Using the decorator can help your code be more concise and readable. Ultimately, you write fewer lines of code by leveraging Python decorators.

Nevertheless, Python decorator isn’t a trivial concept to comprehend. Understanding Python decorators requires building blocks, including closure, function as an object, and deep knowledge of how Python code is executed.

Many online resources talk about Python decorators, but many tutorials only provide some sample code walkthroughs. Reading sample code could help to get a basic understanding of Python decorator. When it comes time to implement your decorator, we might still need clarification and clarity about the decorator concept and yet have to refer to the online resource to refresh our memory for detail.

Reading code sometimes doesn’t deepen memory but seeing images does. In this article, I want to help you understand Python decorator with some visualization and fun examples.

Python Functions are Objects | Image by Author

If we went to Python programming 101 class, we might get a drawing like this. We defined a variable, a representational name used to reference an object. The variable foo points to an object at a given time. It can be a list [1,2,3,4] , it can be a dictionary [“city”: “New York”], or it can a string “I like dumplings”

A less covered discussed topic in Python is that the variable foo can also point to a function add() . When the variable refers to a function, the foo can pass around the same way you used other types like int, str, list, or dict.

What does that mean I can pass foo around? This unlocks us to pass a function as an argument. Furthermore, we can return a function as a return type

# Python Functions are Objects
def I_am_1():
return 1

def return_1():
return I_am_1

def add(a, b):
return a + b

foo = add

del add
## let remove the original defintion

print(foo(return_1()(),2))
## ouput 3
print(foo(foo(return_1()(),2),3))
## output 6

Code walkthrough

  • Function Definition: we defined three functions called add which adds two objects; I_am_1() which simply returns number 1; return_1 which return the function I_am_1
  • Interesting Fact: Then we point to another variable foo to it. To prove that function is an object like the other types, we can even remove the original function reference named add . The rest of the code is still runnable due to foo refers to the object for the function.
  • Result Explain: return_1()() looks wired at first. If we take a closer look, it is the same way as we are calling the function, return_1() is I_am_1 as it just returns this function. In this case, we can think return_1()()=1mentally, so it does not surprise us the foo(1,2) gives an output of 3. We can also pass foo(1,2) to another function. In this case, we passed it to itself. Since foo(1,2)=3 , the outer function will operate as foo(3,3) . To get the result, we can pass the entire function with its arguments over and let the program execute at runtime to decipher everything.

Code Visualisation

Pass Function to another Function | Image By Author

A Python decorator transforms the functionality of the original object without modifying the original one. It is syntax sugar that allows users to extend the object’s ability more conveniently instead of duplicating some existing code. Decorators are a pythonic implementation of the decorator design pattern (Note: python decorator isn’t precisely the same as decorator design pattern).

We have discussed that we can return a function as a return type. Now, we extend that idea to demonstrate how a decorator works: we can return a function as a return type within another function.

To make our example more fun, we can create our decorator like a wrapper around a function and later stacks the multiple decorators.

Let’s first define our functions and use the example of making some dumplings. 🥟🥟🥟

## Python Decorators Basic -- I love dumplings
def add_filling_vegetable(func):
def wrapper(*args, **kwargs):
print("<^^^vegetable^^^>")
func(*args, **kwargs)
print("<^^^vegetable^^^>")
return wrapper

def add_dumplings_wrapper(func):
def wrapper(*args, **kwargs):
print("<---dumplings_wrapper--->")
func(*args, **kwargs)
print("<---dumplings_wrapper--->")
return wrapper

def dumplings(ingredients="meat"):
print(f"({ingredients})")

add_dumplings_wrapper(add_filling_vegetable(dumplings))()
# <---dumplings_wrapper--->
# <^^^vegetable^^^>
# (meat)
# <^^^vegetable^^^>
# <---dumplings_wrapper--->
add_dumplings_wrapper(add_filling_vegetable(dumplings))(ingredients='tofu')
# <---dumplings_wrapper--->
# <^^^vegetable^^^>
# (tofu)
# <^^^vegetable^^^>
# <---dumplings_wrapper--->

Code walkthrough

  • Function Definition: we defined three functions called add_filling_vegetable, add_dumplings_wrapper, and dumplings . Within add_filling_vegetable and add_dumplings_wrapper, we define a wrapper function that wraps around the original function passed as an argument. Additionally, we can do additional things. In this case, we print some text before and after the original function. If the original function also defined parameters, we can pass them in from the wrapper function. We also leverage the magic *args and **kwargs to be more flexible
  • Result Explain
  1. we can call the default ingredients for meat by using the default one add_dumplings_wrapper(add_filling_vegetable(dumplings))() , we can see the function are chaining together. It’s not readable, which we will fix shortly with decorator syntax. The core idea here is that we keep passing the function object as an argument to another. The function does two things: 1) continue doing the unmodified original function; 2) execute additional code. The execution of the entire code is like a round trip.
add_dumplings_wrapper(add_filling_vegetable(dumplings))() | Image By Author

2. for add_dumplings_wrapper(add_filling_vegetable(dumplings))(ingredients=’tofu’) it changes the default ingredients from meat to tofu by passing an additional argument. In this case, *args and **kwargs is very useful for resolving the complexity. The wrapper functions don’t have to unwrap to know the context of the argument. As a decorator, it is safe to pass down the actual function without modifying them.

add_dumplings_wrapper(add_filling_vegetable(dumplings))(ingredients=’tofu’)

So far, we haven’t touched the decorator syntax yet. Let’s make a small change in how we define the original function and call it fancy_dumplings.

## Stack decorator, the order matters here
@add_dumplings_wrapper
@add_filling_vegetable
def fancy_dumplings(ingredients="meat"):
print(f"({ingredients})")

fancy_dumplings()
# <---dumplings_wrapper--->
# <^^^vegetable^^^>
# (meat)
# <^^^vegetable^^^>
# <---dumplings_wrapper--->

fancy_dumplings(ingredients='tofu')
# <---dumplings_wrapper--->
# <^^^vegetable^^^>
# (tofu)
# <^^^vegetable^^^>
# <---dumplings_wrapper--->

Now decorator simplifies how we call all the functions and makes it more readable. We only need to call fancy_dumplings only once. it is much cleaner visually than nesting multiple functions horizontally.

Great! How to create the Python decorators are clear to us now. What can I use decorators for?

There are many potential practical use cases for Python decorators. It makes your code easier to read and dynamically. Note you don’t necessarily need to go decorators. We can always implement a wrapper around another function to achieve the same goal. The decorator is just syntactic sugar.

You can build your time decorator to evaluate the performance for a given function. The following is a timer example from Primer on Python Decorators

import functools
import time

def timer(func):
"""Print the runtime of the decorated function"""
@functools.wraps(func)
def wrapper_timer(*args, **kwargs):
start_time = time.perf_counter()
value = func(*args, **kwargs)
end_time = time.perf_counter()
run_time = end_time - start_time
print(f"Finished {func.__name__!r} in {run_time:.10f} secs")
return value
return wrapper_timer

It measures the time to execute a given function. Let’s stack it with our dumplings example.

@timer
@add_dumplings_wrapper
@add_filling_vegetable
def fancy_dumplings(ingredients="meat"):
print(f"({ingredients})")

fancy_dumplings()
# <---dumplings_wrapper--->
# <^^^vegetable^^^>
# (meat)
# <^^^vegetable^^^>
# <---dumplings_wrapper--->
# Finished 'wrapper' in 0.0000334990 secs

You can keep stacking the decorators to achieve your goal by simply calling the original function without worrying about wrapping other functions around when calling, as we have provided the sequence of the decorator when we define the original function.

Photo by Markus Spiske on Unsplash

There are many possibilities you can leverage the Python decorators. Essentially, it’s a wrapper to alert the behavior of the original function. It depends on your perspective to judge how practical the decorator is, but it shouldn’t be a syntax that feels foreign to you. I hope with some visualization, the concept of decorators becomes more straightforward to understand. Please let me know if you have any comments on this story.

FOLLOW US ON GOOGLE NEWS

Read original article here

Denial of responsibility! Techno Blender is an automatic aggregator of the all world’s media. In each content, the hyperlink to the primary source is specified. All trademarks belong to their rightful owners, all materials to their authors. If you are the owner of the content and do not want us to publish your materials, please contact us by email – [email protected]. The content will be deleted within 24 hours.

Leave a comment