More decoration

Christopher Schmidt explains the traditional approach to wrapping functions and methods, one I use regularly; Python's built-in property function, as a decorator, produces read-only properties, but can provide read-write property access when used traditionally.

Are decorators merely cosmetic? I'm of the opinion that some syntaxes are better than others. You're likely to agree that:

>>> 1 + 2
3

is more concise, readable, and intuitive than

>>> int(1).__add__(2)
3

but may not agree that Python's decorators are a syntactic improvement. PEP 318 was hotly debated, but is final; decorators are in, and they'll be expanded in 3.0.

The motivation for decorators is compelling:

The current method of applying a transformation to a function or method places the actual transformation after the function body. For large functions this separates a key component of the function's behavior from the definition of the rest of the function's external interface. For example:

def foo(self):
    perform method operation
foo = classmethod(foo)

This becomes less readable with longer methods. It also seems less than pythonic to name the function three times for what is conceptually a single declaration. A solution to this problem is to move the transformation of the method closer to the method's own declaration. The intent of the new syntax is to replace:

def foo(cls):
    pass
foo = synchronized(lock)(foo)
foo = classmethod(foo)

with an alternative that places the decoration in the function's declaration:

@classmethod
@synchronized(lock)
def foo(cls):
    pass

Even if calling code isn't exactly broken, wrapping a function more than likely changes the function's signature in some way; keeping all signature specification (such as it is in Python) at the head of a function is a good thing and requires some syntax like that of PEP 318. GIS programmers who've come to Python in the past several years via ArcGIS should get with @. If you can't or won't, that's fine too; there's another way, as Christopher shows.

On "prettier code": all else being equal, prettier code is more readable code. It's code that can teach, that can be more easily modified by others. In some ways, better code.

One downside of the decorator syntax: ability to test decorators in a doctest eludes me. The following:

def noisy(func):
    """
    >>> @noisy
    >>> print foo()
    Blah, blah, blah
    1
    """
    def wrapper(*args):
        print "Blah, blah, blah"
        return func(*args)
    return wrapper

@noisy
def foo():
    return 1

fails:

Exception raised:
    Traceback (most recent call last):
    ...
       @noisy

    ^
     SyntaxError: unexpected EOF while parsing

Could be ignorance on my part.

Comments

Re: More decoration

Author: Christopher Schmidt

def noisy(func):
    """
    >>> @noisy
    ... def foo():
    ...     return 1
    >>> foo()
    Blah, blah, blah
    1
    """
    def wrapper(*args):
        print "Blah, blah, blah"
        return func(*args)
    return wrapper

Re: More decoration

Author: Sean

Moving foo inside the docstring doesn't help. It was being found by doctest before.

Re: More decoration

Author: Christopher Schmidt

I guess I don't know what you're trying to do. I'm not trying to test 'foo', I'm trying to test 'noisy'. So I define a 'foo' that uses 'noisy', and I test that 'foo' does what I want. (In this case, foo does nothing except return '1'; this would still be the case even if the real 'foo'.
disciplina:~ crschmidt$ python foo.py -v
Trying:
    @noisy
    def foo():
        return 1
Expecting nothing
ok
Trying:
    foo()
Expecting:
    Blah, blah, blah
    1
ok
1 items had no tests:
    __main__
1 items passed all tests:
   2 tests in __main__.noisy
2 tests in 2 items.
2 passed and 0 failed.
Test passed.
So, I guess I don't know what you're trying to do.

Re: More decoration

Author: Sean

Ah, I finally see what's up. My original docstring text
  >>> @noisy
  >>> def foo():
  ...
is invalid Python, which is obvious if you type it into a prompt:
  >>> @noisy
  ...
Thanks for the help, Christopher. And yes, best to define the foo mock within noisy's docstring test.