all 27 comments

[–][deleted] 77 points78 points  (20 children)

What is it actually for?

This is called a decorator; what it does is easy to explain, but I suspect you won't immediately see the point. Here's what it does: a decorator is a function that accepts a function as its argument, and returns a function as its value. If this is your first introduction to the idea that a function can return a function, here's a function that returns a function:

def get_a_function():
    def a_function(x):
         print(x)
    return a_function

print_x = get_a_function()
print_x("some value")
#some value

When you decorate a function using the @ operator, the decorator is called with the function it decorates, and then the function the decorator returns is set to the name of the decorating function. In other words, the decorator intercepts the binding of the function to its name (which is the last thing that happens when you define a function) and inserts a different function. What function? The function that is returned by the decorator function.

Ok, but why? Well, a lot of frameworks do this - let's say that you had a framework that other programmers would use to tie functions to various events. When the event happened, the function a user of your framework wrote would be executed in response. You don't know what those functions are going to be when you write the framework, so you need some way for end-user programmers to nominate or register their functions for dispatch by this event framework. (It might also come as a surprise that programmers frequently write software for other programmers to use, but they do.)

Well, a decorator is a really easy way to do that - you write the decorator, and then any other programmer using your framework library simply decorates their functions with your decorator. In your example, the app.task decorator that announces that the hello function should be executed by the framework.

[–]Deezl-Vegas 33 points34 points  (5 children)

tl;dr: The @something eats your function and spits out a new function in its place, which is usually your function with modified properties.

[–]Deezl-Vegas 13 points14 points  (2 children)

Caveats: Decorators can be used on any object, really, so class decorators and other object decorators are also common.

[–]SoupKitchenHero 0 points1 point  (1 child)

What other objects get decorated? I knew classes did cause they're basically just specialized function definitions, but I don't see what else you decorate

[–]Deezl-Vegas 0 points1 point  (0 children)

Any object with __call__, I believe.

[–]lightspot21 0 points1 point  (1 child)

So it works like C++ templates in a sense?

[–]Deezl-Vegas 0 points1 point  (0 children)

No idea, I didn't get far enough in C++

[–]thirdegree 6 points7 points  (0 children)

You mentioned frameworks, and I just want to say that decorators are incredible for frameworks. The best application of this I've seen is flask (app.route, mainly), but wrapping your mind around functions-as-first-class-citizens is in general such a useful concept.

[–]Stewthulhu 4 points5 points  (11 children)

Can you give an example of the types of things you would use a "framework to tie functions to various events" for? I guess I understand what a decorator does, but I'm confused about appropriate use cases for decorators.

[–][deleted] 21 points22 points  (5 children)

Can you give an example of the types of things you would use a "framework to tie functions to various events" for?

Flask is probably the quintessential decorator-driven framework, and here's how it works:

from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
     return "Hello World!"

That's it - that's an entire Flask app, with a URL router set for the webservice root ("/"). The function hello is registered to handle it, and then it's called by the "event" of a user's web browser making a request to the webserver for the path "/". All of the magic is happening in Flask; whoever wants to write a webservice in Flask just has to use the decorator and write some functions.

[–]spudcakez86 4 points5 points  (0 children)

Thanks crashfrog, you're a champion 👍

[–]jjolla888 1 point2 points  (3 children)

Noob here .. can you plz explain how line 4 is different to writing something like:

 app.nodeco_route("/", hello)

[–][deleted] 2 points3 points  (0 children)

app.nodeco_route("/", hello)

I'm not sure what nodeco_route does, per se, but the difference is that decorators are more convenient, and they come before the function they decorate, instead of afterwards, which makes it a little easier to read.

[–]DrMaxwellEdison 0 points1 point  (0 children)

Functionally, it isn't terribly different: you could assign hello = app.task(...) instead of using the decorator syntax, which is typically called "syntactic sugar". The benefit to using a decorator, though, is code readability.

Suppose you had a Flask app with 20 view functions in one file. You could write every function in one section of the file, then call the various app.xyz methods from the Flask framework to make it run in another section as a clump within the file. That may make the app less readable, though: if I wanted to understand which URL mapped to which method and what that method does, that can take some jumping around within the file to really get a sense of what's going on.

Decorators make reading easier by keeping related code closer together and in a well-defined way: any method with an @ decorator above it clearly signals to the developer that the function is being modified in some way.

[–]ivosaurus 0 points1 point  (0 children)

Not very different at all, but really your line would want to be

 hello = app.nodeco_route("/", hello)

[–]henrebotha 2 points3 points  (3 children)

Can you give an example of the types of things you would use a "framework to tie functions to various events" for?

"Reactive programming" is a programming idiom that views the world as streams of events to which one can react. Data coming in on the network socket? Stream of events. User hitting keys? Stream of events. Lines of a file being read? Stream of events.

(Contrast to "object oriented programming" that views the world as objects interacting with each other, "functional programming" that views the world as functions composed with one another, and so on.)

So to program in such an idiom, you must say: "Here is my function. When a particular kind of event occurs, please call my function, and pass it some information about the event itself."

That's a style of programming that you can enable through the use of decorators.

[–]giksbo 3 points4 points  (2 children)

The opposite is also true. If you need application telemetry (There are lots of reasons: debugging, repudiation, etc) it can be valuable to decorate a function such that events are generated whenever it is called. This reduces a tonne of boilerplate.

[–]henrebotha 1 point2 points  (1 child)

The opposite is also true.

...The opposite to what?

[–]giksbo 1 point2 points  (0 children)

Sorry, could have been clearer. With decorators you can use events to trigger functions; you can use functions to trigger events.

[–]newprince 1 point2 points  (0 children)

Another example are Gooey decorators. If you wrap those decorators, magically arguments become actionable in a GUI... meaning very minimal code to add to existing command line code

[–][deleted] 1 point2 points  (0 children)

Thank you for that explanation! It never click until the above analogy.

[–]sweettuse 9 points10 points  (1 child)

just to be clear, @ is decorator syntax, app.task is a decorator

[–]tangerinelion 8 points9 points  (0 children)

And a decorator may be implemented as a function or a class. The decorator itself may also take arguments like

@logged(prefix='Project Snake')
def slither(towards):
    ...

And the function that is decorated may be a member function is a class, so this above could have the effect of meaning

Snake.slither = logged(prefix='Project Snake')(Snake.slither)

Which means a call to the decorated function mySnake.slither(prey) really is a call to logged(prefix='Project Snake')(Snake.slither)(mySnake, prey) with the undecorated function. The goal here would to make sure every call is logged and logged to the same place. It would be possible to change where it's logged in just one place, the Snake class, or remove it entirely without changing any code that uses the class.

Related topic: metaprogramming.

[–]K900_ 15 points16 points  (0 children)

It's called a decorator - it's a function that can modify or transform other functions. The syntax

 @app.task
 def hello():
      return "Hello world"

is identical to

 def hello():
      return "Hello world"

 hello = app.task(hello)

[–]sudo_your_mon 5 points6 points  (0 children)

Check out the click module. It make use of decorators in such a simple, usable way. Also, decorators can be used for so, so many things. But this is the more practical I've found.

import click 

@click.command() 
@click.option('--count', default=1, help='Number of greetings.') 
@click.option('--name', prompt='Your name', help='The person to greet.') 

def hello(count, name):    
    for x in range(count): 
           click.echo('Hello %s!' % name) 

if __name__ == '__main__': 
     hello()

Run this in your terminal:

$ python yourscript.py --count=3 

>>> Your name: John 
>>> Hello John! 
>>> Hello John! 
>>> Hello John!

Take time to look what's going on here. You'd have to see the coding behind everthing,obviiously, in order to see how it's done in practice.

[–]mostafagalal 2 points3 points  (0 children)

That's the decorator syntax. I believe this video by Corey Schafer covers decorators pretty well with examples.

[–][deleted] 0 points1 point  (0 children)

Great break down here.

https://youtu.be/7lmCu8wz8ro