289 lines
8.4 KiB
Plaintext
289 lines
8.4 KiB
Plaintext
PEP: 318
|
||
Title: Function/Method Decorator Syntax
|
||
Version: $Revision$
|
||
Last-Modified: $Date$
|
||
Author: Kevin D. Smith <Kevin.Smith@theMorgue.org>,
|
||
Jim Jewett <jimjjewett@users.sourceforge.net>,
|
||
Skip Montanaro <skip@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
|
||
|
||
* work for arbitrary wrappers, including user-defined callables and
|
||
the existing builtins ``classmethod()`` and ``staticmethod``
|
||
|
||
* work with multiple wrappers per definition
|
||
|
||
* make it obvious what is happening; at the very least it should be
|
||
obvious that new users can safely ignore it when writing their own
|
||
code
|
||
|
||
* not make future extensions more difficult
|
||
|
||
* be easy to type; programs that use it are expected to use it very
|
||
frequently
|
||
|
||
* not make it more difficult to scan through code quickly. It should
|
||
still be easy to search for all definitions, a particular
|
||
definition, or the arguments that a function accepts
|
||
|
||
* not needlessly complicate secondary support tools such as
|
||
language-sensitive editors and other "`toy parser tools out
|
||
there`_"
|
||
|
||
.. _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]:
|
||
...
|
||
|
||
2. 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]:
|
||
...
|
||
|
||
3. 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")]:
|
||
...
|
||
|
||
4. 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:
|