Python decorator? - can someone please explain this? [duplicate]
Asked Answered
V

7

50

Apologies this is a very broad question.

The code below is a fragment of something found on the web. The key thing I am interested in is the line beginning @protected - I am wondering what this does and how it does it? It appears to be checking that a valid user is logged in prior to executing the do_upload_ajax function. That looks like a really effective way to do user authentication. I don't understand the mechanics of this @ function though - can someone steer me in the right direction to explain how this would be implemented in the real world? Python 3 answers please. thanks.

@bottle.route('/ajaxupload', method='POST')
@protected(check_valid_user) 
def do_upload_ajax():
    data = bottle.request.files.get('data')
    if data.file:
        size = 0
Vindicable answered 21/8, 2012 at 0:10 Comment(4)
Some good answers - is "protected" significant?Vindicable
Nope, you could name it @discombobulated if you wanted to. Decorators are just functions.Transmutation
Take a look at this. It's a great explanation of decorators in python.Fusee
Since some people like to learn in video format, here is the best explanation I've watched of Python decorators. In this video (click link to be taken to the start of the topic) James Powell takes you though the entire history of decorators so you get a very clear picture of the why and how. youtu.be/cKPlPJyQrt4?t=3099Snowmobile
T
51

Take a good look at this enormous answer/novel. It's one of the best explanations I've come across.

The shortest explanation that I can give is that decorators wrap your function in another function that returns a function.

This code, for example:

@decorate
def foo(a):
  print a

would be equivalent to this code if you remove the decorator syntax:

def bar(a):
  print a

foo = decorate(bar)

Decorators sometimes take parameters, which are passed to the dynamically generated functions to alter their output.

Another term you should read up on is closure, as that is the concept that allows decorators to work.

Transmutation answered 21/8, 2012 at 0:26 Comment(0)
K
22

A decorator is a function that takes a function as its only parameter and returns a function. This is helpful to “wrap” functionality with the same code over and over again.

We use @func_name to specify a decorator to be applied on another function.

Following example adds a welcome message to the string returned by fun(). Takes fun() as parameter and returns welcome().

def decorate_message(fun):

    # Nested function
    def addWelcome(site_name):
        return "Welcome to " + fun(site_name)

    # Decorator returns a function
    return addWelcome

@decorate_message
def site(site_name):
    return site_name;

print site("StackOverflow")

Out[0]: "Welcome to StackOverflow"

Decorators can also be useful to attach data (or add attribute) to functions.

A decorator function to attach data to func

def attach_data(func):
       func.data = 3
       return func

@attach_data
def add (x, y):
       return x + y

print(add(2, 3))
# 5    
print(add.data)
# 3
Ken answered 29/9, 2017 at 7:1 Comment(0)
E
9

The decorator syntax:

@protected(check_valid_user) 
def do_upload_ajax():
    "..."

is equivalent to

def do_upload_ajax():
    "..."
do_upload_ajax = protected(check_valid_user)(do_upload_ajax)

but without the need to repeat the same name three times. There is nothing more to it.

For example, here's a possible implementation of protected():

import functools

def protected(check):
    def decorator(func): # it is called with a function to be decorated
        @functools.wraps(func) # preserve original name, docstring, etc
        def wrapper(*args, **kwargs):
            check(bottle.request) # raise an exception if the check fails
            return func(*args, **kwargs) # call the original function
        return wrapper # this will be assigned to the decorated name
    return decorator
Extravagate answered 21/8, 2012 at 0:56 Comment(2)
Does "protected" mean anything specific?Vindicable
@Duke: It is just a name. You could use any.Extravagate
F
7

A decorator is a function that takes another function and extends the behavior of the latter function without explicitly modifying it. Python allows "nested" functions ie (a function within another function). Python also allows you to return functions from other functions.

Let us say, your original function was called orig_func().

def orig_func():       #definition 
    print("Wheee!")

orig_func()            #calling 

Run this file, the orig_func() gets called and prints. "wheee".

Now, let us say, we want to modify this function, to do something before this calling this function and also something after this function.

So, we can do like this, either by option 1 or by option 2

--------option 1----------

def orig_func():
    print("Wheee!")

print "do something before"
orig_func()
print "do something after"

Note that we have not modified the orig_func. Instead, we have made changes outside this function. But may be, we want to make changes in a such a way that when orig_func is called, we are able to do something before and after calling the function. So, this is what we do.

--------option 2----------

def orig_func():
    print "do something before"
    print("Whee!")
    print "do something after"

orig_func()

We have achieved our purpose. But at what cost? We had to modify the code of orig_func. This may not always be possible, specially, when someone else has written the function. Yet we want that when this function is called, it is modified in such a way, that something before and/or after can be done. Then the decorator helps us to do this, without modifying the code of orig_func. We create a decorator and can keep the same name as before. So, that if our function is called, it is transparently modified. We go through following steps. a. Define the decorator. In the docorator, 1) write code to do something before orig_func, if you want to. 2) call the orig_func, to do its job. 3) write code to do something after orig_func, if you want to. b. Create the decorator c. Call the decorator.

Here is how we do it.

=============================================================

#-------- orig_func already given ----------
def orig_func():
   print("Wheee!")

#------ write decorator ------------
def my_decorator(some_function):
    def my_wrapper():
        print "do something before"   #do something before, if you want to
        some_function()
        print "do something after"    #do something after, if you want to
    return my_wrapper

#------ create decorator and call orig func --------
orig_func = my_decorator(orig_func)   #create decorator, modify functioning 
orig_func()                           #call modified orig_func

===============================================================

Note, that now orig_func has been modified through the decorator. So, now when you call orig_func(), it will run my_wrapper, which will do three steps, as already outlined.

Thus you have modified the functioning of orig_func, without modifying the code of orig_func, that is the purpose of the decorator.

Flashover answered 3/12, 2017 at 7:15 Comment(2)
Great answer ! Explained well.Blink
can you add a few lines after the last code block, to show how it looks like using @ and decorator grammar?Beanfeast
H
3

Decorator is just a function that takes another function as an argument

Simple Example:

def get_function_name_dec(func):
  def wrapper(*arg):
      function_returns = func(*arg)  # What our function returns
      return func.__name__ + ": " + function_returns

  return wrapper

@get_function_name_dec
def hello_world():
    return "Hi"

print(hello_world())
Harms answered 4/9, 2019 at 11:34 Comment(2)
Thanks for your reply, but in the 7 years since I asked the question my Python skills have improved and I now understand decorators. Your answer will no doubt be useful to others though. :-)Vindicable
That was the main purpose of it. I was looking for a nice, and small example when googling decorators. I hope it will fill the gap ;)Harms
H
1

A decorator is the function which takes another function as an argument to change its result or to give it some effect.

For example, with the code below:

# 4 + 6 = 10

def sum(num1, num2):
    return num1 + num2

result = sum(4, 6)
print(result)

We can get the result below:

10

Next, we created minus_2() to subtract 2 from the result of sum() as shown below:

# (4 + 6) - 2 = 8

def minus_2(func): # Here
    def core(*args, **kwargs):
        result = func(*args, **kwargs)
        return result - 2
    return core

def sum(num1, num2):
    return num1 + num2
    
f1 = minus_2(sum)
result = f1(4, 6)
print(result)

In short:

# ...

result = minus_2(sum)(4, 6)
print(result)

Then, we can get the result below:

8

Now, we can use minus_2() as a decorator with sum() as shown below:

# (4 + 6) - 2 = 8

def minus_2(func):
    def core(*args, **kwargs):
        result = func(*args, **kwargs)
        return result - 2
    return core

@minus_2 # Here
def sum(num1, num2):
    return num1 + num2
    
result = sum(4, 6)
print(result)

Then, we can get the same result below:

8

Next, we created times_10() to multiply the result of minus_2() by 10 as shown below:

# ((4 + 6) - 2) x 10 = 80

def minus_2(func):
    def core(*args, **kwargs):
        result = func(*args, **kwargs)
        return result - 2
    return core

def times_10(func): # Here
    def core(*args, **kwargs):
        result = func(*args, **kwargs)
        return result * 10
    return core

def sum(num1, num2):
    return num1 + num2

f1 = minus_2(sum)
f2 = times_10(f1)
result = f2(4, 6)
print(result)

In short:

# ...

result = times_10(minus_2(sum))(4, 6)
print(result)

Then, we can get the result below:

80

Now, we can use times_10() as a decorator with sum() above @minus_2 as shown below:

# ((4 + 6) - 2) x 10 = 80

def minus_2(func):
    def core(*args, **kwargs):
        result = func(*args, **kwargs)
        return result - 2
    return core

def times_10(func):
    def core(*args, **kwargs):
        result = func(*args, **kwargs)
        return result * 10
    return core

@times_10 # Here
@minus_2
def sum(num1, num2):
    return num1 + num2

result = sum(4, 6)
print(result)

Then, we can get the same result below:

80

Don't forget that if a function has multiple decorators as above, they are executed from the bottom to the top as shown below:

# ((4 + 6) - 2) x 10 = 80

@times_10 # 2nd executed.
@minus_2  # 1st executed. 
def sum(num1, num2):
    return num1 + num2

Then, we can get the same result below as we've already seen it in the above example:

80

So, if we change the order of them as shown below:

# ((4 + 6) * 10) - 2 = 98

@minus_2  # 2nd executed.
@times_10 # 1st executed. 
def sum(num1, num2):
    return num1 + num2

Then, we can get the different result below:

98

Lastly, we created the code below in Django to run test() in transaction by @tran:

# "views.py"

from django.db import transaction
from django.http import HttpResponse

def tran(func): # Here
    def core(request, *args, **kwargs):
        with transaction.atomic():
            return func(request, *args, **kwargs)
    return core

@tran # Here
def test(request):
    person = Person.objects.all()
    print(person)
    return HttpResponse("Test")
Hay answered 12/11, 2022 at 19:4 Comment(0)
P
0

I'm going to use a code to response this.

What I need?: I need to modify math.sin()'s definition to add 1 always to the sine of a value

Problem: I do not have math.sin() code

Solution: Decorators

import math

def decorator_function(sin_function_to_modify):
    def sin_function_modified(value):
        # You can do something BEFORE math.sin() == sin_function_to_modify call
        value_from_sin_function = sin_function_to_modify(value)
        # You can do something AFTER math.sin() == sin_function_to_modify call
        new_value = value_from_sin_function + 1;
        return new_value;

    return sin_function_modified

math.sin = decorator_function(math.sin);

print(math.sin(90))

Return of math.sin(90) before implement decorators: 0.8939966636005579

Return of math.sin(90) after implement decorators: 1.8939966636005579

Proconsulate answered 29/8, 2021 at 2:19 Comment(1)
Your powers of necromancy are indeed strong.Vindicable

© 2022 - 2024 — McMap. All rights reserved.