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