Salta ai contenuti. | Salta alla navigazione

Informativa breve
Questo sito si avvale di Cookies necessari al funzionamento ed utili alle finalità illustrate nella Informativa estesa.
Chiudendo questo banner, si acconsente all’uso dei Cookies.

IPnext Blog

Notizie e riflessioni su Internet e sulle tecnologie IP.

Python Decorators for noobs: a simple tutorial

1. Intro.

Decorators are a useful addition to the Python programming language, but can be difficult to grasp at first. If you already read some articles on Python decorators and found them hard to understand, this tutorial, aimed at beginners, will attempt at explaining how you can use them in a simple and, hopefully, comprehensible way.

2. So what are decorators?

Put simply, "decorating" a function means embedding it inside another "decorator" function or class. If you had a previous exposure to Django, the popular Web application framework written in Python, you'll probably have encountered the '@require_login decorator'. Its purpose is quite simple: any 'view' method decorated with it will be accessible only to authenticated users.

@require_login
def my_index_page():
    html = "<p>Welcome to my boring webpage, user!</p>"
    return html

Basically, this code means: "the user will be required to login before he or she can access the page content." In this tutorial, we will be writing a very similiar decorator function.

3. Our first decorator.

If you ever used Windows 7 or Vista you probably encountered a security mechanism called 'UAC'. It attempts to protect the operating system from malware by requiring the user to explicitly 'accept' or 'deny' the execution of any program that requires super-user privileges.

"Windows needs your permission to continue. Continue or Cancel?". We will replicate this functionality with a decorator function called "authorize", that will require the user to confirm the execution of any function decorated with it.

4. Let's code it!

First of all, let's define a very simple function:

def say_hello():
    print "Hello world!"

The say_hello() command should be fairly self explanatory! We will now port the UAC mechanism to Python, call it 'authorize()', and decorate our function with it. Let's fire up the Python interpreter and enter the following code:

def authorize(command):
    if raw_input('>>> Do you wish to execute this command? ') == "y":
        return command()

It's a very simple function: a command will be passed to our decorator function as a value and a confirmation input will be requested from the user; if the user enters the letter 'y' as a confirmation, the command will be executed. Let's decorate our say_hello() function and see if it works...

@authorize
def say_hello():
    print "Hello world!"

The output will be:

[Do you wish to execute this command?] y
Hello world!

Congratulations! You've just written your first decorator. Take a moment to let this sink in... then start reading the next chapter.

5. Extending our very own UAC

So we've written and understood our first decorator, but let's admit it: it's not very useful. For instance, I'm not sure it would work if we passed it a slightly more complex function, such as one that takes parameters.

text = "Hello world!"
@authorize
def say_text(text):
    print text

[Do you wish to execute this command?] y
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in authorize
TypeError: say_text() takes exactly 1 argument (0 given)

Obviously, the decorator didn't get our only argument, the "Hello world!" string. What to do? You'll be happy to know that we can pass arguments to decorators too; like this:

@authorize(text)

Now, what we must do is rework our code, so our decorator can capture both the command and the argument, and we can achieve this by 'nesting' our decorator function:

def authorize(text):

    def inner(command):
        if raw_input('[Do you wish to execute this command?] ') == "y":
            return command(text)

    return inner

Our 'outer' authorize() function will accept an argument, the "text" variable, while the nested inner() function will capture the command to be decorated. Let's try this out:

raw_input('Text to print: ')
@authorize(text):
def say_text(text):
    print text

It will now work correctly.

6. Decorator classes

Decorators can be classes too. Let's rewrite our previous, nested decorator function as a class:

class authorize:

    def __init__(self, text):
        self.text = text

    def __call__(self, command):
        if raw_input('[Do you wish to execute this command?] ') == "y":
            return command(self.text)

The __init__ method will set the class attribute "self.text" with our decorator argument. We have also defined via the __call__ method what will happen when the decorator object will be called: if the condition is True (the user inputs 'y'), the method will return the executed command. Let's try it:

text = raw_input('Text to print: ')
@authorize(text)
def say_text(text):
    print text

It works! But, wait... this is far from pretty or elegant. Weren't decorators introduced in the language to make code more beautiful and pythonic? The answer is, of course, yes, and indeed the last listing has a lot of repetitions and feels a little bloated. This is obviously not a good thing and we need to amend it. But how?

7. A better use of decorator classes

The solution is to pass the command to the decorator class as an attribute, in the __init__ method. What a mouthful! It's easier done than said; just look:

class authorize:

    def __init__(self, command)
        self.command = command

    def __call__(self, *args):
        if raw_input('[Do you wish to execute this command?] ') == "y":
        return self.command(*args)

The first part should be obvious now: we define "authorize" class __init__ method with the 'command' argument. When we will use the decorator, the decorated function will be passed as an argument to the decorator class and be 'assimilated', Borg-style, inside an attribute. The second part is simple: when the decorator function will be called, the __call__ method will take any argument (the special '*args' keyword) and feed it to the command to be returned. In this case, it will be the text that the user will input. This means we don't have to ask our decorator to pass arguments anymore:

text = raw_input('Text to print: ')

@authorize
def say_text(text):
    print text

What? Nothing happened! Our decorated function was not executed! Why? This is startling... Well, what happened behind the scenes is that our decorator object *did* get initialized, but wasn't called... yet! We need to call it first:

say_text(text)

It should work now! Better still, our code is much more simple, elegant and intuitive. But the best part is that it is compatible with almost any kind of function, and is fully reusable.

8. The Final Test

Open your favourite text editor, such as Vim, TextEdit, Notepad++ or simply Notepad and key in the following code: (Note: please make this last, final effort and type it in - don't just copy and paste it!)

class authorize:

    def __init__(self, command):
        self.command = command

    def __call__(self, *args, **kwargs):
        if raw_input('>>> Do you wish to execute this command? ') == "y":
            return self.command(*args, **kwargs)

text = raw_input('Text to print: ')

@authorize
def say_text(text):
    print text

print "Executing first command."
say_text(text)

filename = raw_input('File to print: ')

@authorize
def read_file(filename):
    try:
        fd = open(filename)

    except IOError as e:
        print e
    else:
        for line in fd.readlines():
            print line
    fd.close()

print "Executing second command."
read_file(filename)

Save it as a file named "deco5.py" (well, after all this is the fifth and last decorator we will write together). '*args' and '**kwargs' are magic keywords that act as a sort of "passthrough conduit" for all arguments the decorated functions are called with. The second decorated function "read_file" will take the name of a file inside the same directory and print it. Now execute our "deco5.py" script with the command

python deco5.py

Hint: when the program asks you "Enter file name: ", just write "deco5.py"... very meta! As you have verified in person, our decorator class can decorate two very different functions... and many, many more!

9. The end

Well done: you have completed this tutorial on decorators. I hope you didn't find it boring and helped you understand how decorators can be good friends that help us write beautiful and elegant Python programs! Code can be poetry! You can download all the source code used in this tutorial here. If you have any questions or corrections, please write them in the comments and I'll be more than happy to reply. Thanks to cheater99 in the #python irc channel on irc.freenode.net, David and itachi_ in the comments for the suggestions and corrections. Happy coding :)

Azioni sul documento

I canadesi danno l'addio all'elenco telefonico cartaceo

In Canada, l'azienda che produce gli elenchi telefonici ha annunciato che ne sospenderà' la distribuzione nelle sei città' maggiori del paese, se non su richiesta esplicita dei singoli utenti. Verranno risparmiate migliaia di tonnellate di carta stampata, oltre all'energia spesa e l'inquinamento prodotto per consegnarla. Gli abbonati dovranno recarsi online per consultare le pagine bianche.

Azioni sul documento

Scopri l'intruso

Leggo oggi su MetroMilano:
La proposta del consiglio regionale a sostegno di formati aperti e software libero e' decaduta al termine della legislatura. Nessuno ne ha parlato in campagna elettorale, salvo il promotore del gruppo di lavoro che scrisse la proposta e che aveva radunato aziende come IBM, Novell e persino Microsoft. La nuova legge avrebbe reso obbligatoria la scelta del software libero laddove fosse stato in grado di garantire le stesse prestazioni del software proprietario.
Per fortuna il Difensore Civico Regionale la pensa diversamente, insieme alle regioni dell'Abruzzo e della Emilia Romagna; hanno adottato o sono in procinto di adottare software libero nei loro uffici.

Azioni sul documento