Python Decorators, Wrapped With Goodness
June 7th, 2008 by ScottK | | Filed in PythonThis post is really the first part of two. Here I’ll talk about what is this Python decorator that everyone is raving about. How does it work. What is it really. The second part I’ll show how to pass arguments and classes between Python decorators so that not only are the decorators executed but the original code block is executed.
Two weeks ago I was put on a Twitter project written in Python and had a requirement of specifically using decorators for logging. I didn’t know anything about this strange functionality and was given a very brief overview of what they were. After the project meeting I hit the interwebs and played with some toy code to figure out just how they work I finally got it figured out to the point that “it worked”.
I was still left feeling like I didn’t understand what Python decorators were and really what the need for them are. Even worse I questioned the need for them anyhow. A short case
def global_some_function(fn):
def wrappedfn(*args, **kwargs):
#do stuff here
return wrappedfn
class base(object):
@global_function
def base_method(self):
pass
class ChildClass(base):
def call_base(self):
base.base_method()
So this is were I really felt like I was violating a bunch of programming rules. I mean I call ChildClass.call_base(), which then calls it’s parent class base_method. Yet that is intercepted by the Python decorator and global global_some_function. So effectively using this decorator the child class is not totally object oriented, and I would feel a lot more happier just having the Base.base_method class do the work instead of the decorator.
Feeling un-satisfied, confused, and like I had dirty code I pressed on and completed the project. The next project consisted of taking a Queue of classes, performing an action on them in a iteration, and then sorting based upon the result of the actions. Almost immediately I realized that this is were the power and flexibility of Python decorators comes in.
I mean based up the result of the action the object may be put back in the Queue, or it may go to another Queue for further processing. The object shouldn’t care about what Queue it’s in, so I needed a way to wrap in this functionality shared by by all Queues. Hitting the interwebs to truly understand what and how Python decorators work yeilded many examples but not a clear cut understanding of how decorators work.
So here my friends is how I figured out what, and how Python decorators work.
First and foremost when I say Python decorators I am not referring to the Decorator Design Pattern. Although I have found to be effectively the same. The Python decorator being a symbolic tag signifying to the Python interpreter that the following method is to be wrapped within an function or class for further processing. So while the Python decorator creates that link to another function, the other function then becomes the Decorated Design Pattern following that pattern.
def foo(fn):
print “Hello”
@foo
def bar(some_arg):
print some_arg
bar(”world”)
What would you expect with the above example? I initially expected the result to print foo(bar(”world”)), however that was not the case as only “Hello” was the result. This tripped me up for sometime and it’s were the online examples and documentation lacked. The reason is due to the decorator function “foo”. There are no functionalities to actually execute the original function “bar”. This pretty much where all the tutorials left off; if you’ve read this far you probably want me to get to the point now huh?
As I stated earlier Python decorators are not design patterns. They are merely a signal to the Python interpreter to wrap the call in other signified functions. There are at least two ingrained decorators that Python ships with: @staticmethod; @classmethod, but as programmers we’ll be dealing with our own custom decorators.
Using the Python decorator is relatively simple. Just declare the decorator with the @ symbol preceding the method to be wrapped. So:
def foo():
print “Hello World”
@foo
def bar():
pass
bar()
To the Python interpreter bar get wrapped with the function foo whenever the method bar is executed. So calling bar and following the order of exectution look s like this foo(bar). There is certainly no reason not to use only one to make if even more flexible:
def foo(fn):
print “world”
def bar(fn):
print “Hello”
@foo
@bar
def something():
print “I’m alive”
So executing something() would follow the order of execution of foo(bar(something)) would print:
Hello
World
but strangely “I’m alive” did not print! Why is that? It’s because wrapping the something function only sends the pointer to the function to the decorators, they are in charge of actually executing the received function. The other huge problem is how to deal with function arguements passed, and even bigger than that how to pass classes to global decorators while retaining the object.
Even though the name implies, Python decorators are not the design pattern. However usage is the same. Signify to the interpreter tho wrap this executable code within this executable code by using the @ character preceding the wrapping code block and preceding the code block to be wrapped.
This post is merely an introduction to what the Python decorators are and how they can be used. I’m following up this post with how to pass arguments through these decorators to really gain the power afforded by the decorator and to make your libraries more extend able. This post is here.
