33
votes

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
6
Some good answers - is "protected" significant?Duke Dougal
Nope, you could name it @discombobulated if you wanted to. Decorators are just functions.Blender
Take a look at this. It's a great explanation of decorators in python.Jaydip Kalkani
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=3099CodyBugstein

6 Answers

39
votes

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.

15
votes

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
8
votes

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
6
votes

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.

5
votes

First, we need to understand why we need Decorator.

Need for decorator: We want to use the function of already built-in Library from Python which can perform our desired task.

Problems: But problem is that we don't want exact function output. We want customized output. The trick is that we can't change the original code of the function. here decorator comes to our help.

Solutions: Decorator takes our required function as input, wraps it in a wrapper function and does 3 things:

  1. do something before.
  2. then call the desired function().
  3. do something after.

Overall code:

def my_decorator(desired_function):
    def my_wrapper():
        print "do something before"   #do something before, if you want to
        desired_function()
        print "do something after"    #do something after, if you want to
    return my_wrapper

desired_func = my_decorator(desired_func())   #create decorator
desired_func()                           #calling desired_func()   
1
votes

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())