Thursday, November 11, 2004

Using 2.4 decorators with 2.2 and 2.3

In my post yesterday on generic functions, I mentioned that you could use PyProtocols' new decorators in Python 2.2 and 2.3 as well as 2.4, by using an alternative syntax.

What I neglected to mention, however, was that this works only for PyProtocols' "magic" decorators, that are specifically designed for this purpose. So, I got some comments from folks who were trying to use other decorators in 2.2 and 2.3, and of course it wasn't working for them. So, here are a couple of tricks you can use, if you'd like to use decorator syntax in Python 2.2 or 2.3:

  • Use dispatch.as() from the CVS version of PyProtocols, as in:

    import dispatch
    class Foo(object):
    [dispatch.as(classmethod)]
    def something(cls, etc):
    """This will be a classmethod"""

    You can pass multiple decorators to dispatch.as(), and they will be executed in the same order as they are for 2.4 decorators (i.e. rightmost-first).

  • Or, you can use protocols.advice.add_assignment_advisor() to implement your own "magic" decorators. Despite the intimidating name, all you need to know is how to write a simple callback, not unlike a standard 2.4 decorator. Here's the source of dispatch.as(), showing a simple callback implementation:
    def as(*decorators):
    """Use Python 2.4 decorators w/Python 2.2+"""
    if len(decorators)>1:
    decorators = list(decorators)
    decorators.reverse()

    def callback(frame,name,value,old_locals):
    for d in decorators:
    value = d(value)
    return value

    return add_assignment_advisor(callback)
    Essentially, you just create a callback function that does the real work of the decorator. For simple decorators, you only need the value parameter in the callback, which is the object being decorated. More sophisticated decorators can use the old_locals parameter to get access to a previous definition of the named object. Note, however, that your callback should always use the supplied frame and/or old_locals in place of attempting to use sys._getframe() directly; this is because the frame your callback is actually invoked from can vary, depending on whether 2.4 '@' syntax is in use. add_assignment_advisor() hides this difference from you by making sure you get a reference to the frame where the function definition is occurring, regardless of which syntax is in use.

Finally, it's important to note that these "magic" decorators use a very sneaky hack: they abuse the sys.settrace() debugger hook to track whether assignments are taking place. Guido takes a very dim view of this, but the hook's existing functionality isn't going to change in 2.2, 2.3, or 2.4, so don't worry about it too much. This is really a trick to get "early access" to decorators, and the 2.4 lifecycle will be plenty long enough to get our code switched over to 2.4 syntax. Somewhere around Python 2.5 or 2.6, add_assignment_advisor() can drop the magic part and just be a backward compatibility wrapper for the decorators that use it.

Oh, and one more thing... you don't need to install PyProtocols to use add_assignment_advisor(), as it doesn't depend on anything else in PyProtocols. You can just swipe that one function and copy it into your own project.