Notizie e riflessioni su Internet e sulle tecnologie IP.
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:
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:
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
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 :)
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.