On Jul 23, 2:13 am, james_027 <[EMAIL PROTECTED]> wrote: > Hi, > > I am learning python by learning django, and I stumble upon decorator > which is very cool, any beginners resources for python decorators, > although I can google it, I just want to get a good tutorial for this > topic. > > Thanks > james
Remember that functions in Python are created when their containing scope is parsed. Let's suppose you want a way to help you debug function calls. This should catch any uncaught exceptions, log a message to the logging system, and start a pdb interpreter. It should allow the SystemExit exception to continue to propogate without doing those actions. The following example would do that appropriately: def HelpDebug(func, *args, **keyargs): "Assist with debugging a function." # The func argument is a Pythong function object # arg is the positional arguments that follow the # first parameter, and keyargs has the keyword arguments. try: returnValue = func(*args, **keyargs) return returnValue except SystemExit: raise # Reraise the system exit exception except Exception, error: from logging import DEBUG, log from sys import exc_info import pdb log(DEBUG, "Caught Exception: %s", error) # Start the debugger at the place where the # exception occurred pdb.post_mortem(exc_info()[2]) return # Nothing to return when an exception occurred def DivXY(x, y): "Divides X by Y" return x / y # Debug the following calls HelpDebug(DivXY, 5.0, 2.1) # This will succeed HelpDebug(DivXY, 10.0, 0.0) # Causes a ZeroDivisionError exception There's a serious problem, though. If you'd like to remove the debugging effect, you need to change all your "HelpDebug(DivXY, xvalue, yvalue)" calls to "DivXY(xvalue, yvalue)". Besides, you're trying to get the result of DivXY, not HelpDebug. The calling code shouldn't care about HelpDebug at all. We could do change HelpDebug so it always calls DivXY, but then we need to create a new function for each one we want to debug. This is where nested scopes come in. Other code should care about calling DivXY, but not HelpDebug. Furthermore, the caller shouldn't be able to casually tell the difference between calling a regular DivXY or the HelpDebug'ed DivXY. Well, if the caller shouldn't be able to casually tell the difference between DivXY with or without the debugging code, then we need to be able to do the following: value1 = DivXY(5.0, 2.1) # The regular call value2 = HelpDebugDivXY(5.0, 2.1) # The debugged call We could create a special "HelpDebugDivXY" function for every such function, but that would quickly exhaust our patience, and introduce lots of extra code that can have bugs. Python has nested scoping, though. If you nest a function inside another function, the inner function has access to the names in the outer function. That doesn't sound too spectacular at first. HOWEVER, in Python functions are objects! The outer function can return the inner function object. Even though the outer function has returned, /the inner function still has access to the outer function's names/! For example: >>> def GetPrintXFunc(x): ... "Returns a function that takes no parameters, but prints the argument" ... # Inner function starts just below ... def PrintX(): ... "Prints the object bound to the name x in enclosing scope." ... print "X is", x ... # A function object is now bound to the name "PrintX" ... return PrintX ... >>> PrintSpam = GetPrintXFunc("Spam") >>> PrintPi = GetPrintXFunc(3.14159) >>> PrintSpam() X is Spam >>> PrintPi() X is 3.14159 >>> Both PrintSpam and PrintPi keep access to their enclosing scope's arguments! In Python, the "def" statement is actually a statement... and IT ISN'T EVALUATED UNTIL ITS ENCLOSING SCOPE IS EXECUTED. Take that in. That is entirely unlike C or C++, where a function definition is evaluated at compile time. In Python, all statements are evaluated at run time. This works even for nested classes. Here's the modified code to help us debug: def WrapWithHelpDebug(func): """Returns a function object that transparently wraps the parameter with the HelpDebug function""" # The def statement actually defines a function object and binds # it to a name. Since it is nested in this function, the def # statement isn't evaluated until def HelpDebug(*args, **keyargs): "Assist with debugging a function." # The func argument is a Pythong function object # arg is the positional arguments that follow the # first parameter, and keyargs has the keyword arguments. try: # The name "func" comes from the outer scope, WrapWithHelpDebug returnValue = func(*args, **keyargs) return returnValue except SystemExit: raise # Reraise the system exit exception except Exception, error: from logging import DEBUG, log from sys import exc_info import pdb log(DEBUG, "Caught Exception: %s", error) # Start the debugger at the place where the # exception occurred pdb.post_mortem(exc_info()[2]) return # Nothing to return when an exception occurred # Back in the WrapWithHelpDebug scope. # HelpDebug is now a function objected defined in this scope. def DivXY(x, y): "Divides X by Y" return x / y DebugDivXY = WrapWithHelpDebug(DivXY) # Debug the following calls DivXY(5.0, 2.1) # This will succeed DebugDivXY(10.0, 0.0) # Causes a ZeroDivisionError exception Wow, that's pretty easy, isn't it! But, it's not as easy as it could be. Let's say that DivXY is used a lot, and you want to debug all calls to it. Rather than change the calls to "DivXY" to "DebugDivXY" everywhere, we can simply assign the wrapped function object to the DivXY name. Instead of: DebugDivXY = WrapWithHelpDebug(DivXY) We use: DivXY = WrapWithHelpDebug(DivXY) Now, all calls to DivXY call the HelpDebug object that uses our original DivXY function! This is *exactly* what a decorator does. Instead of writing: def DivXY(x, y): "Divides X by Y" return x / y DivXY = WrapWithHelpDebug(DivXY) You write: @WrapWithHelpDebug def DivXY(x, y): "Divides X by Y" return x / y To turn off the help-debug behavior, simply comment out the decorator. This has turned out to be a bit long, so I'll finish up in the next post.... --Jason -- http://mail.python.org/mailman/listinfo/python-list