Decorators in Python
Tags: python, algorithms, objects, oop
Published: 2021-09-21 00:07:48

Hello to everyone,

In this article, I will try to explain the logic of decorators, which is a confusing subject for some, as simply as I can. But for better understanding, let's first take a look at the function and the objects that hold them.

```
def say_hello():
    return "hello" 
```    

When we define a function like the one on the above, Python creates a function object with its own tag. So you can imagine an object with a name and some properties that returns 'Hello' when invoked. So how do we get to this object?

``` 
print(say_hello())        
print(say_hello) 
```  

If we run the functions sequentially as on the above, we get the following outputs in the same order.

` 
hello
<function say_hello at 0x000001BA3891D1F0> 
`

When we run it as say_hello(), it is understood that it is a function and our 'Hello' value is returned. But if we print the name of the function whose __name__ property is say_hello, we get the output <function say_hello at 0x000002135AE8D160>. We reached the object, understood that it was a function object, and learned its address. Then I can keep this address in another variable I want.

** So what happens if I assign a value like say_hello2 = say_hello? **

True, both show the same object as output when printed because here 'say_hello' gave me the address of the function object it was holding and python assigned it to 'say_hello2'. If I assigned say_hello2 = say_hello() then my say_hello2 value would output 'Hello' because I would have assigned a value to the function, not the object address.

* Now, with what we have learned, let's slowly move towards the decorator logic. *

```
def say_hello_decorator(func):
    return func

def say_hello():
    return 'Hello ' 

say_hello = say_hello_decorator(say_hello)
print(say_hello()) 
```

When I write a code like the one on the above, the say_hello_decorator function only returns the func it contains, while the say_hello function returns the value 'Hello'.

Let's pay attention to the part `say_hello = say_hello_decorator(say_hello)`. When this block runs, python assigns the address of the function object, which is the value of say_hello, to another variable named 'func', which is the local variable in the function. So now func = <function say_hello at 0x000002135AE8D160>. Afterwards, when the say_hello_decorator function runs, it returns the address it keeps in the func and assigns the same address to the say_hello variable. When we run it, the say_hello function starts to run and we get the 'Hello' output.

** Hah, here we come to the main part, Python says, instead of doing this every time, use decorators. **

```
def say_hello_decorator(func):
    return func

@say_hello_decorator #say_hello = say_hello_decorator(say_hello)
def say_hello():
    return 'Hello ' 

print(say_hello()) 
```

As @say_hello_decorator I can overwrite any function I want and it replaces it as a parameter. The purpose of using Decorators is to write more readable and clean code. Now let's complicate things a bit:

```
def say_hello_decorator(func):
    def wrapper():
        return func
    return wrapper

@say_hello_decorator 
def say_hello():
    return 'Hello ' 

print(say_hello()) 
```

Here we gave say_hello_decorator the address of say_hello. So, will we get the same address again or is this the address of another function?

As you can see it is different. The point to note here is the func() statement. Now our decorator starts working with the func local variable that holds the old address. The wrapper function runs the function object at the address we sent and returns 'Hello'. After that, I can now do whatever I want to my function in the wrapper as below, and I can get parameters.

```
def say_hello_decorator(func):
    def wrapper(name):
        val = func(name)
        print("Wrapper works")
        return val
    return wrapper

@say_hello_decorator 
def say_hello(name):
    return 'Hello ' + name

print(say_hello("Berkin Öztürk")) 
```


To review one last time, at first we had a function called say_hello and its return value 'Hello'. By adding a decorator (say_hello_decorator) to this function, we have defined the expression say_hello = say_hello_decorator(say_hello). In other words, we sent a function address into a function. Thus, in the say_hello_decorator function, we either send the function we sent the address back to the say_hello function without making any changes, or we add new features and run the say_hello's function at the first address. But other than that, if you use the val = expression as in the example above, you can perform certain operations without running the function in the first address.

In summary; The say_hello_decorator function takes the address that say_hello holds, gives it the address of the wrapper, and says get it, run it before or after some operations, the first function will work where necessary, don't worry.

This article was written with the aim of explaining what I understand both to myself and to people who want to understand the logic of the decorator. I hope that this article, in which I combined my own words with the resources I researched on the Internet, will be useful to you...