decorators tutorials
Jason
tenax.raccoon at gmail.com
Mon Jul 23 13:57:25 EDT 2007
On Jul 23, 11:25 am, Jason <tenax.racc... at gmail.com> wrote:
> On Jul 23, 2:13 am, james_027 <cai.hai... at gmail.com> 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
With apologies, there is an error in my previous WrapWithHelpDebug.
The last part of the function should read:
# Back in the WrapWithHelpDebug scope.
# HelpDebug is now a function objected defined in this scope.
return HelpDebug # This line was missing
Now, my prior post shows how decorators work. Functions are objects
created by Python's def statement. They have access to the names in
all their enclosing scopes: the module's global scope and any
functions that they are nested in.
The WrapWithHelpDebug decorator wraps a function so uncaught
exceptions get caught, logged, and the Python debugger is started.
You won't notice much output from the logger usually, because the
default log level is set to "DEBUG". That might be fine, but maybe
you want to pass the debugging level as a parameter.
import logging
def WrapWithHelpDebug(func, logLevel):
"""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 log
from sys import exc_info
import pdb
log(logLevel, "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.
return HelpDebug
def DivXY(x, y):
"Divides X by Y"
return x / y
DivXY = WrapWithHelpDebug(DivXY, logging.DEBUG)
# Debug the following calls
DivXY(5.0, 2.1) # This will succeed
DivXY(10.0, 0.0) # Causes a ZeroDivisionError exception
So, if we just need to add a new parameter, how do we do that with a
decorator? Could we use "@WrapWithHelpDebug(logging.DEBUG)"?
No. Remember, the decorator symbol is just a bit of syntactic sugar.
It turns:
@WrapWithHelpDebug(logging.DEBUG)
def DivXY(x, y):
"Divides X by Y"
return x / y
Into:
def DivXY(x, y):
"Divides X by Y"
return x / y
DivXY = WrapWithHelpDebug(logging.DEBUG)(DivXY)
Oops! That implies that we're calling "WrapWithHelpDebug" with our
logging parameter. To accommodate the extra parameter, we're going to
need to use Python's nested scopes again. Here's a final version that
does everything we want:
import logging
def HelpDebugDecorator(logLevel):
"Returns a function object that will properly decorate a
function."
# Note that logLevel used in the nested function HelpDebug
# comes from this scope.
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 log
from sys import exc_info
import pdb
log(logLevel, "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.
return HelpDebug
# Back in the HelpDebugDecorate scope.
# Return the WrapWithHelpDebug function object
return WrapWithHelpDebug
@HelpDebugDecorator(logging.ERROR)
def DivXY(x, y):
"Divides X by Y"
return x / y
#DivXY = WrapWithHelpDebug(DivXY, logging.DEBUG)
# Debug the following calls
DivXY(5.0, 2.1) # This will succeed
DivXY(10.0, 0.0) # Causes a ZeroDivisionError exception
Just a few final notes about decorators:
o Multiple decorators can be applied to a function.
o Any callable (such as classes and instances with a __call__
method) can be used as a decorator. This can be tremendously
useful if you need to save state between multiple decorated
calls. For example, you might want the log when certain functions
are called. Perhaps you want to indent each successive call, and
dedent with every decorated return. Use an instance to keep
track of the indents and dedents.
I hope that I haven't made any egregious errors in these two posts.
However, decorates are a very nifty Python feature, and I hope this
helps you understand them.
--Jason
More information about the Python-list
mailing list