[Python-Dev] PEP 318 - posting draft (original) (raw)

Skip Montanaro skip at pobox.com
Tue Mar 23 12:02:06 EST 2004


Here's the current state of PEP 318. I have to take a break from this to get some real work done and may not get back to it in a major way for awhile. I received a significant rewrite of Kevin Smith's most recent version from Jim Jewett and incorporated much of what he wrote, modifying a lot of it along the way, but have still not digested everything he sent me.

I've tried to reflect the concensus which seems to be emerging on the python-dev list, though I suspect I've done a poor job of that. The discussions there and on comp.lang.python have ranged far and wide and thus resist summary in a finite amount of time. I recommend interested comp.lang.python readers spend some time in the python-dev archives for February and March if they find major fault with the current state of the proposal.

If you post corrections or comments to either list I should see them.

Skip Montanaro


PEP: 318 Title: Function/Method Decorator Syntax Version: Revision:1.5Revision: 1.5 Revision:1.5 Last-Modified: Date:2004/03/2316:41:17Date: 2004/03/23 16:41:17 Date:2004/03/2316:41:17 Author: Kevin D. Smith <Kevin.Smith at theMorgue.org>, Jim Jewett <jimjjewett at users.sourceforge.net>, Skip Montanaro <skip at pobox.com> Status: Draft Type: Standards Track Content-Type: text/x-rst Created: 05-Jun-2003 Python-Version: 2.4 Post-History: 09-Jun-2003, 10-Jun-2003, 27-Feb-2004, 23-Mar-2004

Abstract

The current method for declaring class and static methods is awkward and can lead to code that is difficult to understand. Ideally, these transformations should be made at the same point in the code where the declaration itself is made. This PEP introduces new syntax for transformations of a declaration.

Motivation

The current method of applying a transformation to a function or method places the actual translation 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. While the new syntax is not yet final, the intent 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::

def foo(cls) using [synchronized(lock), classmethod]:
    pass

Background

There is general agreement that syntactic support is desirable to the current state of affairs. Guido mentioned syntactic support for decorators_ in his DevDay keynote presentation at the 10th Python Conference, though he later said it was only one of several extensions he proposed there "semi-jokingly". Michael Hudson raised the topic_ on python-dev shortly after the conference, attributing the bracketed syntax to an earlier proposal on comp.lang.python by Gareth McCaughan_.

.. _syntactic support for decorators: http://www.python.org/doc/essays/ppt/python10/py10keynote.pdf .. _10th python conference: http://www.python.org/workshops/2002-02/ .. _michael hudson raised the topic: http://mail.python.org/pipermail/python-dev/2002-February/020005.html .. _he later said: http://mail.python.org/pipermail/python-dev/2002-February/020017.html .. _gareth mccaughan: http://groups.google.com/groups?hl=en&lr=&ie=UTF-8&oe=UTF-8&selm=slrna40k88.2h9o.Gareth.McCaughan%40g.local

Design Goals

The new syntax should

.. _toy parser tools out there: http://groups.google.com/groups?hl=en&lr=&ie=UTF-8&oe=UTF-8&selm=mailman.1010809396.32158.python-list%40python.org

Proposed Syntax

The currently proposed syntax is::

def func(arg1, arg2, ...) [dec1, dec2, ...]:
    pass

The decorators are near the declaration of the function's API but are clearly secondary. The square brackets make it possible to fairly easily break long lists of decorators across multiple lines.

Alternate Proposals

A few other syntaxes have been proposed::

def func(arg1, arg2, ...) as dec1, dec2, ...:
    pass

The absence of brackets makes it cumbersome to break long lists of decorators across multiple lines. The keyword "as" doesn't have the same meaning as its use in the import statement.

:: def [dec1, dec2, ...] func(arg1, arg2, ...): pass

This form has the disadvantage that the decorators become visually higher priority than the function name and argument list.

:: def func [dec1, dec2, ...] (arg1, arg2, ...): pass

Quixote's Page Template Language uses this form, but only supports a single decorator chosen from a restricted set. For short lists it works okay, but for long list it separates the argument list from the function name.

:: using: dec1 dec2 ... def foo(arg1, arg2, ...): pass

The function definition is not nested within the using: block making it impossible to tell which objects following the block will be decorated. Nesting the function definition within the using: block suggests block structure that doesn't exist. The name foo would actually exist at the same scope as the using: block. Finally, it would require the introduction of a new keyword.

Current Implementation

Michael Hudson has posted a patch_ at Starship, which implements the proposed syntax and left-first application of decorators::

def func(arg1, arg2, ...) [dec1, dec2]:
    pass

is equivalent to::

def func(arg1, arg2, ...):
    pass
func = dec2(dec1(func))

though without the intermediate creation of a variable named func.

.. _patch: http://starship.python.net/crew/mwh/hacks/meth-syntax-sugar.diff

Examples

Much of the discussion on comp.lang.python and the python-dev mailing list focuses on the use of the staticmethod() and classmethod() builtins. This capability is much more powerful than that. This section presents some examples of use.

  1. Define a function to be executed at exit. Note that the function isn't actually "wrapped" in the usual sense.

:: def onexit(f): import atexit atexit.register(f) return f

def func() [onexit]:
    ...
  1. Define a class with a singleton instance. Note that once the class disappears enterprising programmers would have to be more creative to create more instances. (From Shane Hathaway on python-dev.)

:: def singleton(cls): return cls()

class MyClass [singleton]:
    ...
  1. Decorate a function with release information. (Based on an example posted by Anders Munch on python-dev.)

:: def release(**kwds): def decorate(f): for k in kwds: setattr(f, k, kwds[k]) return f return decorate

def classmethod(f) [release(versionadded="2.2",
                            author="Guido van Rossum")]:
    ...
  1. Enforce function argument and return types.

:: def accepts(*types): def check_accepts(f): def new_f(*args, **kwds): for (a, t) in zip(args, types): assert isinstance(a, t),
"arg %r does not match %s" % (a,t) return f(*args, **kwds) assert len(types) == f.func_code.co_argcount return new_f return check_accepts

def returns(rtype):
    def check_returns(f):
        def new_f(*args, **kwds):
            result = f(*args, **kwds)
            assert isinstance(result, rtype), \
                   "return value %r does not match %s" % (result,rtype)
            return result
        return new_f
    return check_returns

def func(arg1, arg2) [accepts(int, (int,float)),
                      returns((int,float))]:
    return arg1 * arg2

Of course, all these examples are possible today, though without the syntactic support.

Possible Extensions

The proposed syntax is general enough that it could be used on class definitions as well::

class foo(object) [dec1, dec2, ...]:
    class definition here

Use would likely be much less than function decorators. The current patch only implements function decorators.

Copyright

This document has been placed in the public domain.

Local Variables: mode: indented-text indent-tabs-mode: nil sentence-end-double-space: t fill-column: 70 End:



More information about the Python-Dev mailing list