Understand Decorator in Python(part1)

CC(ChenChih)
Chen-Chih’s Portfolio Page
26 min readJan 22, 2024

I have been studying with the decorator, and wish to write about it in this post. Python Decorator is an advanced skill in Python that you need to understand how to use. I will show using some easy examples and concepts to understand it. I don’t use it often, but it’s an important topic in Python.

[Fast overview ]So what and why is using a decorator?

When you have many functions but we want to extend some feature that doesn’t want to modify the original function then you can use the decorator to pass the original function.

Before diving into it, I will explain using normal function, so when you see any code that has a @ this keyword means it has been decorated.

I looked at many tutorial articles and YouTube channels related to it and would like to summarize it. I think it’s confusing thing in the beginning especially when a function passes a function as an argument, so I made some diagrams to illustrate it.

I made several topics before diving into a decorator because it seems like need to have some understanding of the scope, closure, and then decorator, but you can skip this topic also it’s not a must just in case you don’t understand it.

I will categorize decorators into two parts:

Python Decorator(function and class decorator, closure) which is this post

Class Decorator with Properties, types of method(classmethod, static method, instance method): another post

I will write another topic on Class decorators in the next post, which is related to class or OOP(Object-oriented programming). However, I won’t mention a lot of OOP concepts in that section. These two posts are related to the decorator, and it seems like a huge topic so I think it is better to split it into several sections, for easier to read.

So overall if you want to understand the concept of decorator please read this post, and for other class decoratory property please read the class decorator.

As aways I would list the content of this post. I mention many concept before diving into decorator, so you can click on the each link on specfic link that interested.

For source code please refer to this link, you can download it. I put it on Jupyter Notebook/Lab for easy tracking or reading.

Table of Contents :

Note: I have placed ¹² in each heading, 1 means the top of it’s heading, 2. So if i have multiply subheading the 1 is top top heading which will link to the header. The header’s top will link to table of content.

  1. Part1: Understand types of variable Scope
    1. Local scope
    2. Global Scope
    3.Enclose Scope or nonlocal scope
    4. Built-in
    Rule of LEGB
  2. Part2 Understand closure
    1.Closure VS nested function
    2.Remembering values/ Persistent Memory
    Ex1. without passing parameter example
    3.Variable Backpack
    Ex2–1 creating a closure and calling the function
    Ex2 -2 showing the passing value
    4.Reference and return function
    Example with single-function
    Example with nested function
    EX1: without passing a parameter with the calling function
    EX2 without passing a parameter with a referencing function
    Ex3 passing a parameter with a referencing function
    Recap and summary of closure
  3. Part3 decorators
    1. Function as an Argument
    2. Understand how traditional decorator function flow and how it works
    3. Create a decorator with a function
    4. Passing argument using args and kwargs
    5. Wrap built-in
    6. Decorator contains parameters
    7. Create multiple Decorator
    8. Create a Decorator with class
    Ex1: class decorator without pass argument
    Ex2: class decorator with passing argument using **args and **kargs
  4. Examples
    —1 Basic Example
    EX1: Division Two number
    —2 Real-Time Example
    Ex1: l Calculate time for looping
    Ex2:Calculate time to square
    Ex3: Write log to a file
    Ex4: Change a word in the list to capitalize and remove the
  5. Conclusion
  6. Reference

Part1: Understand types of variable Scope¹

There are 4 types of scope variables we need to know and it uses the LEGB rules:

  • Local: contains the name defined inside the current function.
  • Global: contain names that are defined at the top, beginning of the script or module.
  • Enclose: contains a name defined inside a function or called a nested function.
  • Built-in: contain name built-in to Python language such as print() .

Let me show you how we can check variable scope, we can use the dir() to see it, it’s just like a scope inspector. Let me show you how to use dir() . When I use the dir() inside function(), only the name variable which is the local variable will show. From below I define a function, and inside it, I declare a name and print the string with dir() .

So define inside the function and use the dir() it will only show the local variable, whereas if you use dir() outside the function, it will display global scope (global variable and method) and built-in functions which are the default.

def function():
name='hello'
print('inside function: ' , dir())
function()

# output
# inside function: ['name']

Now if I use dir() outside the function, it will display more variables and functions including global scope, and you will see the name variable I declared just now.

Now let's drive into each scope for further understanding of each scope. The most common will be the local and global which I demonstrated just now.

1. Local scope¹

The variable that is defined inside the current function is called local scope. The variable inside the function scope lifetime only variable is inside the function, once the function ends the variable is also not available. You can refer to it as a normal function.

The x variables are only available inside that function, so the lifetime of the x variable is inside the function. This is like a basic function, when declaring a function, inside the function will execute. If you try to access x outside the function it error will occur. So just remember anything defined inside the function will not be able to execute from outside, because the lifetime of the local scope will end when the function ends.

If I try to access x the variable from outside will occur x is not defined as below:

2. Global Scope¹

The first line variable defined at the top or beginning of the module is the global variable. These names or variables are available throughout the script and can be used within this file ONLY. The lifetime will end when the program ends. Let's look at below example:

Y is the global variable declared at the top of the script, which can be used throughout the script. So as you can see from the above code, I declare y outside the function. I am still able to access the y inside of the function inner()because y is the global variable.

  • Now if we refine the y variable inside the function

So when I redefine the y variable inside the function or local scope it will change, which means it will overwrite the y in the function. Any reference to y within the function will now refer to the local variable.

However, when the functioninner() exits the y will go out of scope and y would reference the global y and the value will be 100. So after calling inner() the function, the y global will not get the effect.

So at this point, only the y will be updated inside the local scope but doesn’t affect the global variable, when exiting the local scope y will remain 100.

So when there’s the same variable in the outside function which is global scope, and inside the function which is local scope, then it will use the local scope variable first. I will explain in the LEGB section below telling you why.

  • Modify the global variable y in the local scope using global keyword

Now how fix the previous problem to update global variables in the local scope? If I try to modify it y+10 will be an unboundLocal error, because we can not modify global variables inside the local scope. To modify we need to use the global keyword.

Need to add a global keyword to modify it. This tells the local scope that the y is referring to the y in the global scope. So when we redefine it, it refers to the global y and stored in memory.

So as you can see using the global keyword modifies the global variable, when running y outside the local scope, it will update to 100 because it overwrites the y variable.

3. Enclose Scope or nonlocal scope¹

contains name define inside function (nested function) enclosed function

The z variable it’s local to the outer() function, but it’s nonlocal to the inner() function. For the inner() function z is not the local variable and not a global variable, so it’s called a nonlocal variable or an enclosing variable. Please refer below diagram which will differential each scope

We can’t access z outside the outer() function, so we can access z on both inner() and outer() function.

  • modify enclose variable and nonlocal keyword

if you modify the enclosed variable inside the local scope will occur UnboundLocalError.

- Using nonlocal keyword to modify enclosing variable

So to fix the previous issue, and update the enclosing variable we need to use the nonlocal keyword to modify the enclosed variable in the local scope. So the nonlocal basely means referencing one scope out and reassigning it.

Recap the keyword for global and nonlocal

Nonlocal: to modify the enclosed variable inside the local scope need to use a nonlocal keyword. Think of referencing the outer layer scope variable and reassigning it.

Global: to modify the global variable inside the local scope need to use the global keyword. Think of referencing the first variable and reassigning it.

4. Built-in¹

Built-in scope refers to all variables accessible as soon as we start the Python interpreter, like print().

It can access wherever we want no need to define anything, we can direly use the name, like print('hello'). We can use print directly without defining a function.

Below is the list of built-in scopes, which do not need to import any module as default. This means you can use below this keyword without installing or importing any module.

If you want to see which built-ins are available, you can import the builtins module like below:

Rule of LEGB¹

After understanding different scopes, let's talk about the LEGB rule. When we define a variable how would it know which scope it uses, it uses a LEGB rule. This means it first searches in the scope of this order local>Enclose>Global>built-in and if none of this is found then will give a name error.

I have summarized the LEGB rule in the picture below

Part2 Understand closure ¹

The easiest way to explain closure is it contains a nested function and the inner function that remembers and has access to variables in the local scope or outer function even after the outer function has finished running execute.

What are the criteria to use closure:

- Nested function

- Define value in enclosing scope

- Enclosing function must return nested function (return to reference to function)

Closure VS nested function ¹

Please keep in mind a nested function doesn’t mean it’s a closure, a closure is a nested function that captures value from an enclosing scope or outer function. Let me show an example of the difference between closure vs nest function:

So in the closure, the inner captures the x=5 from the outer function. This capture value is retained in the inner function’s own memory space. When inner function is called it’s able to capture the value and calculate and return the result. Below is a list comparison between nested function and closure.

Remembering values/ Persistent Memory¹

Closures allow nested functions to remember values from their enclosing scope(parent function), even if those values are no longer present in memory.

- Function with Memory: When you have a function that contains variables, those variables are stored in memory.

- Inner Function’s Reference: If you have an inner function inside this function that uses (references) those variables, it forms a closure.

  • Memory Retention: Now, even if the outer function finishes execution, the inner function still “remembers” and has access to those variables. This is because the closure keeps a reference to the variables it needs.

Ex1. without passing parameter example ¹ ²

Let's dive into the code and I will explain step by step for this example:

# Step 1: Call outer_func(), create a closure, and assign the inner() function to my_func
my_func = outer_func()

# Step 2: Inside outer_func(), set the message variable
# Step 2.1: Define message = 'hi'
# Step 3: Create the inner function, but it does not run the code inside yet
# Step 3.1: inner() is defined but not executed
# Step 4: Return the inner function as an object or memory reference
# Step 5: Assign the result (the inner function) to my_func
# Step 6: Call my_func(), which will call the inner function
# Step 7: Print the value of the message variable
# Step 8: Call my_func() again
# Step 9: Print the value of the message variable

As you can see I have ended our outer function, but it’s still remembering and able to access our inner function’s variable it returns it and indeed this is what a closure is. In step 6 and 8 we’re still able to access it and execute the inner function. The reason is my_func is an object that records the inner() function’s memory address, so when using my_func() it will call the inner() function which will print(message) .

Variable Backpack ¹

The nested function captures copies of the values, not just references, allowing access even after the outer function finishes. Even if the outer function finishes execution, the closure can still access these captured values.

Ex2–1 creating a closure and calling the function ¹ ²

def outer_function(x):
def inner_function():
print(x) # Doesn't explicitly return x
return inner_function

closure = outer_function(5)
#call outer_function and assign x=5, return inner object memory addr
closure() #call inner_function()
# Output: 5

Ex2 -2 showing the passing value ¹ ²

is example is related to the previous one, except this one will do the calculation of old value and new value, whereas the previous one just assigns value and displays.

output: 15

Even though outer_function has finished, closure_instance still remembers x (which is 10), the result is 10 + 5 = 15

From this example, you can observe that the value x is stored in memory. Initially, x is set to 10. Even after the outer_function completes its execution, the inner function (closure_instance) captures and retains the original value of x, which is 10. So, when we later call, it uses the stored x from memory, performing the calculation, and providing the result 10+5.

Let breakdown the code:

# Step0: The code is read from top to bottom, outer_function is defined but not executed.
#Step1: call outer_function(10)
#Step2: When you call outer_function(10), it initializes x with the value 10
#Step3,4: inner_function is defined within the scope of outer_function with access to the variable x. inner_function is not executed; instead, it is returned, and its reference is stored in closure_instance. The return essentially links the function’s memory location.
#Step5: assign return of outer_function to closure_instance which is the function object of inner_function
#Step6,7: Call closure_instance(5), it invokes or calls the inner function with the value 5 for y, and since the inner function still has access to the x from its enclosing scope (due to closure), it returns the result 10 + 5.

Reference and return function ¹

I would like to explain the difference between the reference function (memory location return function object) and calling the function

Function with (): calling function. You can refer as basic call function

Function without (): saving function as object and function memory location. You can refer as closure

  • Example with single-function ¹ ²

From above you can see if I called without the parenthesis it will return the function’s object or think as the function memory address, and calling the function will run under the function which is the print.

  • Example with nested function ¹ ²

Case 1: The outer is defined immediately and executes the inner function, and a is assigned a reference to the function outer. So when you print(a) it will give the object of the inner memory location. Now to print the inner(), you can either use print(a()), or print(outer()) .

If you don’t want to display none then you should instead replace the print with the return under inner() function( ex return msg).

Note:

- a and outer: a is a reference to the function object created by outer.

a(): you are executing the outer function, which in turn calls the inner function and prints the value inside it.

Case2:outer is called, and it returns the inner function (without executing it), a is assigned the function inner(reference object of function), and when you later invoke a(), you are calling the function and printing hello.

Overall Case2 is versatile and generally commonly used and is typically used for closure. But the first case will immediately call it, just want to show you we also can achieve it. But overall if using closure is recommended use case 2 example

Returning the function without parentheses returns an object or reference to the function.

Adding parentheses executes or call the function and gives you the result.

  • EX1: without passing a parameter with the calling function ¹ ² ³

So in this example using the return inner() which means the inner function is executed immediately when the outer function is called. Please note this is just like calling a function but not creating a closure.

Step1: call outer_func() will be executed, creating a closure and executing the inner function immediately
Step2: Define outer_func, and assigning message variable
Step3: create a inner() function, which references the free variable message
Step4: Return and immediately execute the inner function
Step5: go to inner(), and print and display out

So in this example using the return inner() which means inner function is executed immediately when outer function is called. Please note this is just like calling a function but not creating a closure.

  • EX2 without passing a parameter with a referencing function ¹ ² ³

This is the same example I mentioned in the Remembering Values/ Persistent Memory section

You can see when we use return inner will return the reference to the inner function address, so if you print() it will display the object. You can also use the the __name__ attribute of a function object in Python which will return the name of the function name.

  • Ex3 passing a parameter with a referencing function ¹ ² ³

return inner_function is returning a reference to the function object inner_function. At this point haven’t executed the function, only created a closure that inner_function has access to the variable x.

Now when you call it with result=closure_instance(5), it will call the function and run it.

Recap and summary of closure¹

So let summary, a closure is a function whose return value depends on the values that are declared outside of the function. So from the below example, the text is declared outside the inner_function. The function can remember the values which are declared outside the function.

def outerfunction(text):
def innerfunction():
print(text)
return innerfunction
outerfunction('Hello')

Let me show some diagram of what closure does

So as you can see when we finished executing the my_fun('key'), all the local variables will be gone, except for the message which is in the free variable stored in memory. The last diagram in the picture is after executing it left with. This is what a closure is doing, it retains information such as a variable or function to be accessed when a function is called later.

Part3 decorators¹

Since already understand the fundamentals of scope and closure, I think it is time to learn how to create a decorator. What is a decorator, and why do we need it?

• A function that takes another function as an argument to add functionality to it or extend a new feature and return another function.

• A function wraps another function to decorate the original function.

• In some cases we don’t want to modify our original function, so then we would instead use the decorator to modify it.

Below is the diagram for creating a decorator, there’re two ways with function and class. We can use it to pass empty arguments and multiply arguments. I will explain each of them below. But I won’t explain a lot about the class part, I mainly focus on the function. However, I will write another post on the class decorator.

Function as an Argument ¹

Every function is an object which allows it to be saved as a variable or passed as an argument to each function. If the function is without adding parenthesis() it will be treated as an object, with parenthesis () will pass as a function.

So as you can see if I remove the () in a function it will print as an object or memory address of the function1. So since we can treat the function as an object then we can pass the function as an argument like this example below:

As you can see function1 is an object, which will print an object or memory, basely it represents function1(). Since it’s an object we can pass through an argument or store it as a variable.

function2 is defined to take an function (funct) as an argument, you are passing the function1 object as argument. Under function2 we execute funct() which essentially is the object of function1 we pass in, so when we call funct() it means calling the function1().This is a basic understanding of taking a Function as an Argument.

understand how traditional decorator function flow and how it works ¹

Now if I have a code look like this, it returns wrapper() immediately calls the wrapper function and returns its result.

Now if we change it to a return wrapper, which will treat it as an object what will happen?

So since x is an object referencing to wrapper() function, indeed it will print an object. To execute the wrapper() function, we need to call the function by x(). As you can see x is the object return wrapper, so to run the function we need to trigger or call the x(). This is a quite common concept of calling a function

Now let's move further to how decorators work by passing a function as an argument

If we want to execute the wrapper function we need to call the wrapper function.

  • Method1: this approach applies the decorator to a function and calls it immediately

this calling wrapper seems weird, basely this syntax applies the decorator immediately and calls the resulting decorated function in a single line. So basely how this work :

1. function(test) is called, which applies the decorator to the test function.

2. This returns the wrapper function, which is essentially the decorated version of the test.

3. function(test)() then calls the wrapper function immediately.

  • Method 2: this approach is useful when you want to reuse the decorated function or when you prefer a more step-by-step approach.

This syntax is stored in a variable and then calls the function. This case is useful when you want to reuse the decorated function, so most people might use this method. This is what a closure is doing as I mentioned in the closure section already. I would like to explain how this code works:

Step1: Call function and pass test function as an argument and assign the result (wrapper function) to f
Step 2: Inside the function, define a wrapper() function that will not do anything, and return the wrapper. Now f will a reference of the wrapper function which is an object or address of the wrapper
Step3: call the wrapper function f since it’s referring to the wrapper function
Step4: execute the wrapper function, which will print start, then it will go to the test() function and print hello, and then return to wrapper function to print 'end'

Note and recap: so basely the f is a variable that stores the wrapper’s object or the memory address which allows you to trigger it by adding the (), so when I use the f(), which is basely calling the wrapper() function which will run below the code. This is how a decorator works.

Create a decorator with a function ¹

The example I showed previously is a way of creating a decorator, but we often don’t create like that, this type of way we called the manual decorator method, or traditional decorator. We often create decorators by adding a @ on top of the function that wanted to decorate or original function.

This is the manual creating decorator

with the @ decorator keyword method

So when you call this function func2(), it went to func2(), but on top , it sees @func which means this function needs to be decorator, then it moves to func() function first.

So the syntax of the @decorator is: @<function name to be decorator>, so in our case, since I want to decorate func2 and func3 we need to add the decorator function with @, so our decorator function is func, so it will be @func.

We don’t need to initial the decorator function and pass a function as an argument, instead just add the @ with the decorator function name(ex:func) on top of the function that want to decorate(ex:func2 or func3), which will look like this:

def decoratorfunction(): #decorator function
pass

@decoratorfunction
func()# normal function that need to decorator

so in the above example, func() need to be decorator so we will place @decoratorfunction on top of it, this means func() this function will be the decorator and move to decorator function first before executing it.

I have drawn an arrow on how the code will flow, so when adding the @decorator. In our case when I call the function func2(), then it sees the @decorator which it knows it’s been the decorator, so it will go to the decorator function and execute it and see func() which will go to the original function and execute it then return and finish the code.

Let's see the comparison between both:

Traditional: need to define the function and pass a function as parameter

Decorator: just ass the @ keyword shortcut.

Traditional

def my_decorator(func):
print('This is Decorator')
return func
def my_func():
print('This is Function')
#initialize
my_func = my_decorator(my_func)
my_func()

@decorator

def my_decorator(func):
print('This is Decorator')
return func

@my_decorator
def my_func():
print('This is Function')

#initialize
#my_func = my_decorator(my_func)
my_func()

Passing argument using args and kwargs ¹

Now if you want to pass an argument to a function it will occur TypeError because your wrapper() doesn’t accept any value.

To solve this you either add a parameter x but this will work with func3, but if you use fun2() which accepts no argument.

So the best solution to this is to add the variable argument *args and variable keyword argument **kwargs , accept all the argument or keyword arguments passed in.

These arguments allow you to add as many arguments as you want. So if you don’t know how many values or arguments you want to add you can use this argument.

def func(f):
def wrapper(*args, **kwargs):
......
return wrapper

Example as below

def decorator_Function(orginal_function):
def wrapper(*args, **kwargs):
print('wrapper execute this before {}'.format(orginal_function.__name__))
return orginal_function(*args, **kwargs)
return wrapper


@decorator_Function
def display_info(name, age):
print(f'hello run argument : {name} {age}')

@decorator_Function
def display():
print('hello world run')

#decoratorFunction=decorator_Function(display)
display_info('CC', 30)
display()
the output of the code

so as you can see if I use the *args and **kwargskeyword argument in my original function display_info() and display() they don’t have to rely on knowing each other’s specific parameters. By utilizing it we use *args as variable parameter argument and **kwargs as variable keyword argument which is more flexible to different inputs.

Wrap built-in ¹

Let me explain what’s the usage or benefit of using the wrap built-in function. When you define a decorator, it’s common to wrap or modify a function inside it. However, without using wraps, the metadata (like __name__, __doc__, etc.) of the original function may be lost, and the decorated function might not behave exactly like you expect.

If I have a code look like this without the wraps built-in, when I use the metadata like __name__, it will return none

mport functools 
def decorator(func):
@functools.wraps(func)
def inner():
str1=func()
return str1.upper()
return inner

@decorator
def greet():
return 'good morning'

print(greet())
print(greet.__name__)
print(greet.__doc__)

Let breakthrough code :

Step1: read this line print(greet()), which will first execute greet()
Step 2: it goes to greet(), sees the @decorator, and goes to decorator(func).
Step 3: decorator(func) wraps the original function func using @functools.wraps(func). The inner()function is created and returned, but it doesn’t immediately execute the original function.

Note for the wraps:

After defining decorator(func), it does not immediately execute anything; instead, it sets up the wrapping with @functools.wraps(func) and creates the inner function, which is returned.

Step4: print(greet()) then goes back and runs greet() which now refers to the inner function. The inner function runs the original func (decorated by the decorator), converts its result to uppercase, and returns it.

Step5: since already created the wraps we can access the metadata.

We apply @functools.wraps(func)in the decorator, which means we can access the metadata like print(greet.__name__) would correctly print greet, preserving the original function name. Also, print(greet.__doc__) would correctly show the original docstring.

Without wraps, the greet function inside decorator would lose its original name and documentation, and you'll see wrapper and None instead. With wraps, it preserves the metadata, so greet.__name__ is greet, and greet.__doc__ is the original docstring which will display the quotation comment ‘‘‘comment’’’.

Metadata is data that provides information about other data. I only show two metadata examples, but there are more as below:

Function name (__name__)

Docstring (__doc__)

Module (__module__)

Argument signature (__annotations__)

Other attributes like __defaults__, __kwdefaults__, and more

So basely using the wraps helps your code to be readable, easier to debug, and clean code.

Decorator contains parameter ¹

We can also contain parameters in the decorator, this is an example:

Step 1: [Execution Begins] print(ordinary()) is executed, initiating the process.

Step2: [Decorator Encountered] The @outer('chenchih') decorator is encountered(by @outer('chenchih')), which know outer('chenchih') is a decorator

Step3 [Outer Function Creates Decorator] The outer function is called with the argument ‘chenchih’, storing it in exp. The outer function is defined and returns the decorator function.

Step4: [Decorator applied] decoratorfunction(ordinary) is called, passing ordinary() as an argument

Step5: [Wrapper Function Created] The wrapper function is created inside decoratorfunction and returned

Step 6: [Wrapper Function Execution] The wrapper function is executed.

Step7 [Original Function Execution] The wrapper function calls ordinary() function and returns the result of ordinary() + exp.

Create multiple Decorator ¹

we also can create multiple decorators an example as below:

This is an example of changing an uppercase string and a split string to a list. So as you can see when we have multiple decorators the first one will execute first, then the next one, as I have marked above.

So @upper_string will first decorator fist which will change string to uppercase, then run @str_split which will split a string into a list, output as ['GOOD', 'MORNING']

Create a Decorator with class ¹

let's show some examples of creating a class decorator for more understanding please refer next post on the class decorator

  • Ex1: class decorator without pass argument ¹ ²
class Decorator:
def __init__(self,func):
self.func=func
def __call__(self):
str1=self.func()
return str1.upper()

@Decorator
def greet():
return 'good morning'
print(greet())

#output
#GOOD MORNING

this is another similar example:

class MyDecorator:
def __init__(self, func):
self.func = func

def __call__(self):
print('This is Decorator')
return self.func()

@MyDecorator
def my_func():
print('This is Function')

# initialize
my_func()

#output
'''
This is Decorator
This is Function
'''

This is a basic example of just calling a function without passing any argument.

  • Ex2: class decorator with passing argument using **args and **kargs ¹ ²
class decorator_class(object):
def __init__(self, orginal_function ):
self.orginal_function=orginal_function
def __call__(self,*args, **kwargs ):
print('call method execute this before {}'.format(self.orginal_function.__name__))
return self.orginal_function(*args, **kwargs)

@decorator_class
def display_info(name, age):
print(f'hello run argument : {name} {age}')

@decorator_class
def display():
print('hello world run')
display_info('CC', 30)
display()
# output
'''
call method execute this before display_info
hello run argument : CC 30
call methid execute this before display
hello world run
'''

Examples¹

in this section, I will put some more examples if you are interested in decorator examples.

Basic Example

  • EX1: Division Two number

this will work only if the numerator must be greater than the denominator(a/b which a >b), if reverse will occur error we need to add some condition, but we can also use the denominator without modifying the code by the decorator.

Adding in decorator for condition:

- So if the numerator is less than the denominator will swap and divide

- If numerator =0 then exit code and print warming

def checing_divide(func):
def inner(a,b):
print("Dividing", a, "by", b)
if b==0:
print('Error, cannot divide by 0')
#a,b=b,a
return exit(2)
elif a<b:
a,b=b,a
print("[Note:]a less than b, swap value")
return func(a,b)
return inner

@checing_divide
def divide(a,b):
return a/b
number1= divide(5,10)
print(number1)

Real-Time Example ¹

  • Ex1: l Calculate time for looping ¹ ²
import time
def timer(func):
def wrapper(*args, **kwargs):
start=time.time()
rv=func()
total=time.time()-start
print('time:', total)
return rv
return wrapper

@timer
def test():
for i in range(100000):
pass
@timer
def test2():
time.sleep(3)
test()
test2()
  • Ex2:Calculate time to square ¹ ²

Without decorator

import time
def calc_square(x):
start=time.time()
result=[]
for i in x:
result.append(i*i)
end=time.time()
print('square ' +' took: '+ str((end-start)*1000)+'milsecond')
return result

array=range(1,100)
square=calc_square(array)

With decorator

import time
def time_it(func):
def wrapper(*args, **kwargs):
start=time.time()
result=func(*args, **kwargs)
end=time.time()
print(func.__name__ +' took: '+ str((end-start)*1000)+'milsecond')
return result
return wrapper
@time_it
def calc_square(x):
result=[]
for i in x:
result.append(i*i)
return result

array=range(1,100)
square=calc_square(array)
  • Ex3: Write log to a file ¹ ²
#write log to a file
def logged(func):
def wrapper(*args, **kwargs):
value=func(*args, **kwargs)
with open('loggile.txt','a+') as fwrite:
functionanme=func.__name__
print(f"{functionanme} return valued {value}")
fwrite.write(f"{functionanme} returned value {value}")
return value
return wrapper
@logged
def func(a,b):
return a+b
print(func(100,20))
  • Ex4: Change a word in the list to capitalize and remove the - ¹ ²
def mapper(fnc):
def inner(list_of_values):
"""This is the inner()"""
#return [fnc(value) for value in list_of_values]
processed_values = []


for value in list_of_values:
processed_value = fnc(value)
processed_values.append(processed_value)
return processed_values
return inner
@mapper
def camelcase(s):
"""Turn strings_like_this into StringsLikeThis"""
#return ''.join([word.capitalize() for word in s.split('_')])
camelcased_words = []
for word in s.split('_'):
camelcased_word = word.capitalize()
camelcased_words.append(camelcased_word)
return ''.join(camelcased_words)

names = [
'rick_ross',
'a$ap_rocky',
'snoop_dogg'
]
#print(camelcase.__doc__)
print(camelcase(names))
# ['RickRoss', 'A$apRocky', 'SnoopDogg']

Conclusion¹

in this post, I mention at least most of the concepts of decorator including scope variable, closure, decorator, decorator with @ keyword, decorator with class, etc.

  • Understanding the main concept of different variable scopes uses the LEBB(Local>Enclose>Global>Built-in) rule as below:
  • I also mention an important concept everything without parenthesis is always an object or reference function memory address, and this is the most important concept in decorator and closure.
  • Whenever see the @ on top of the function then it’s a decorator function. The decorator function means we don’t want to modify the source code or function instead we use the decorator function. image if we have many people in a team if you change the original function code, then everyone needs to modify it, instead using a decorator will decorate the original function that solves this issue.

This is a comparison between different decorators with same result:

I learn most of the concepts from online resources, please reference the below reference for more details.

Reference¹

https://www.programiz.com/python-programming/decorator
https://www.freecodecamp.org/news/python-decorators-explained-with-examples/
https://osf.io/szwhk
https://www.pythontutorial.net/advanced-python/python-closures/
https://www.youtube.com/watch?v=FsAPt_9Bf3U
https://www.youtube.com/playlist?list=PLzgPDYo_3xukWUakgF-OJvDOChq6drPG2
https://www.youtube.com/watch?v=8hWIWyBfdQE&t=334s
https://www.youtube.com/watch?v=yNzxXZfkLUA&t=320s
https://www.youtube.com/watch?v=3XRSULw-HlE
https://www.youtube.com/watch?v=iZZtEJjQLjQ&t=305s
https://www.youtube.com/watch?v=nYDKH9fvlBY
https://www.youtube.com/watch?v=xUxbEy4WSy0

--

--

CC(ChenChih)
Chen-Chih’s Portfolio Page

I self study technology, and likes to exchange knowledge with people. I try writing complex tech blog into simple and various ways for people to understand.