44

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
Super Kai - Kazuya Ito
  • 22,221
  • 10
  • 124
  • 129
Duke Dougal
  • 24,359
  • 31
  • 91
  • 123
  • Some good answers - is "protected" significant? – Duke Dougal Aug 21 '12 at 04:25
  • 1
    Nope, you could name it `@discombobulated` if you wanted to. Decorators are just functions. – Blender Aug 21 '12 at 20:54
  • Take a look at [this](https://gist.github.com/Zearin/2f40b7b9cfc51132851a). It's a great explanation of decorators in python. – Jaydip Kalkani Apr 18 '18 at 05:43
  • 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. https://youtu.be/cKPlPJyQrt4?t=3099 – CodyBugstein Mar 11 '19 at 16:50

7 Answers7

47

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.

Community
  • 1
  • 1
Blender
  • 289,723
  • 53
  • 439
  • 496
19

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
Will Nathan
  • 625
  • 7
  • 13
Tushar Patil
  • 1,419
  • 1
  • 18
  • 21
8

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
jfs
  • 399,953
  • 195
  • 994
  • 1,670
6

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.

M Shiraz Baig
  • 113
  • 1
  • 3
2

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())
mikazz
  • 159
  • 1
  • 11
  • 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. :-) – Duke Dougal Sep 04 '19 at 11:44
  • 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 ;) – mikazz Sep 04 '19 at 12:01
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

Carlos Espinoza
  • 1,115
  • 11
  • 13
0

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")
Super Kai - Kazuya Ito
  • 22,221
  • 10
  • 124
  • 129