Checking in what we have so far. Still digesting some of Jim Jewett's
inputs.
This commit is contained in:
parent
efebd8e76c
commit
b2436faf3b
374
pep-0318.txt
374
pep-0318.txt
|
@ -2,243 +2,281 @@ PEP: 318
|
||||||
Title: Function/Method Decorator Syntax
|
Title: Function/Method Decorator Syntax
|
||||||
Version: $Revision$
|
Version: $Revision$
|
||||||
Last-Modified: $Date$
|
Last-Modified: $Date$
|
||||||
Author: Kevin D. Smith <Kevin.Smith@theMorgue.org>
|
Author: Kevin D. Smith <Kevin.Smith@theMorgue.org>,
|
||||||
|
Jim Jewett <jimjjewett@users.sourceforge.net>,
|
||||||
|
Skip Montanaro <skip@pobox.com>
|
||||||
Status: Draft
|
Status: Draft
|
||||||
Type: Standards Track
|
Type: Standards Track
|
||||||
Content-Type: text/plain
|
Content-Type: text/x-rst
|
||||||
Created: 05-Jun-2003
|
Created: 05-Jun-2003
|
||||||
Python-Version: 2.4
|
Python-Version: 2.4
|
||||||
Post-History: 09-Jun-2003, 10-Jun-2003, 27-Feb-2004
|
Post-History: 09-Jun-2003, 10-Jun-2003, 27-Feb-2004, 23-Mar-2004
|
||||||
|
|
||||||
|
|
||||||
Abstract
|
Abstract
|
||||||
|
========
|
||||||
|
|
||||||
The current method for declaring class and static methods
|
The current method for declaring class and static methods is awkward
|
||||||
is awkward and can lead to code that is difficult to understand.
|
and can lead to code that is difficult to understand. Ideally, these
|
||||||
This PEP introduces possible new syntax which will place the
|
transformations should be made at the same point in the code where the
|
||||||
translation of instance methods to class/static methods at
|
declaration itself is made. This PEP introduces new syntax for
|
||||||
the same point in the code as the method's declaration.
|
transformations of a declaration.
|
||||||
|
|
||||||
|
|
||||||
Motivation
|
Motivation
|
||||||
|
==========
|
||||||
|
|
||||||
The current method of translating an instance method into a
|
The current method of applying a transformation to a function or
|
||||||
class/static method places the actual translation at a different
|
method places the actual translation after the function body. For
|
||||||
point in the code than the declaration of the method. The
|
large functions this separates a key component of the function's
|
||||||
code below demonstrates this.
|
behavior from the definition of the rest of the function's external
|
||||||
|
interface. For example::
|
||||||
|
|
||||||
def foo(self):
|
def foo(self):
|
||||||
perform method operation
|
perform method operation
|
||||||
foo = classmethod(foo)
|
foo = classmethod(foo)
|
||||||
|
|
||||||
When the method is very short, it is easy to look ahead and see
|
This becomes less readable with longer methods. It also seems less
|
||||||
that this is a class method. However, if the method is more than
|
than pythonic to name the function three times for what is
|
||||||
15 lines or so, the translation into a class method is not
|
conceptually a single declaration. A solution to this problem is to
|
||||||
obvious. A solution to this problem is to move the translation
|
move the transformation of the method closer to the method's own
|
||||||
of the method to the same point as the method's declaration.
|
declaration. While the new syntax is not yet final, the intent is to
|
||||||
The proposed syntax, shown in the example below, is discussed
|
replace::
|
||||||
in the following sections.
|
|
||||||
|
|
||||||
def foo(self) as synchronized(lock), classmethod:
|
def foo(cls):
|
||||||
perform method operation
|
pass
|
||||||
|
foo = synchronized(lock)(foo)
|
||||||
|
foo = classmethod(foo)
|
||||||
|
|
||||||
|
with an alternative that places the decoration in the function's
|
||||||
|
declaration::
|
||||||
|
|
||||||
Proposal
|
def foo(cls) using [synchronized(lock), classmethod]:
|
||||||
|
pass
|
||||||
|
|
||||||
Probably the simplest way to place the decorator that translates
|
Background
|
||||||
an instance method to a class/static method is illustrated in the
|
==========
|
||||||
code below.
|
|
||||||
|
|
||||||
def classmethod foo(self):
|
There is general agreement that syntactic support is desirable to the
|
||||||
perform method operation
|
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`_.
|
||||||
|
|
||||||
The code in this example will simply perform the following.
|
.. _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
|
||||||
|
|
||||||
def foo(self):
|
Design Goals
|
||||||
perform method operation
|
============
|
||||||
foo = classmethod(foo)
|
|
||||||
|
|
||||||
This syntax does not introduce any new keywords and is completely
|
|
||||||
backwards compatible with any existing code. The word between the
|
|
||||||
'def' and the actual name of the method is simply a reference to
|
|
||||||
a callable object that returns a new function reference.
|
|
||||||
This syntax could also be extended to allow multiple function
|
|
||||||
decorators in the form of a space delimited list as follows:
|
|
||||||
|
|
||||||
def protected classmethod foo(self):
|
The new syntax should
|
||||||
perform method operation
|
|
||||||
|
|
||||||
which would be equivalent to the current form:
|
* work for arbitrary wrappers, including user-defined callables and
|
||||||
|
the existing builtins ``classmethod()`` and ``staticmethod``
|
||||||
|
|
||||||
def foo(self):
|
* work with multiple wrappers per definition
|
||||||
perform method operation
|
|
||||||
foo = protected(classmethod(foo))
|
|
||||||
|
|
||||||
While this syntax is simple and easy to read, it does become
|
* make it obvious what is happening; at the very least it should be
|
||||||
cluttered and more obscure if you wish to allow arguments to be
|
obvious that new users can safely ignore it when writing their own
|
||||||
sent to the function decorator.
|
code
|
||||||
|
|
||||||
def synchronized(lock) classmethod foo(self):
|
* not make future extensions more difficult
|
||||||
perform method operation
|
|
||||||
|
|
||||||
Instead of placing the decorators in front of the function name,
|
* be easy to type; programs that use it are expected to use it very
|
||||||
a better place might be after it, as shown below. The word 'as' is
|
frequently
|
||||||
added simply as a separator to assist in readability.
|
|
||||||
|
|
||||||
def foo(self) as synchronized(lock), classmethod:
|
* not make it more difficult to scan through code quickly. It should
|
||||||
perform method operation
|
still be easy to search for all definitions, a particular
|
||||||
|
definition, or the arguments that a function accepts
|
||||||
|
|
||||||
This syntax is quite clear and could probably be interpreted
|
* not needlessly complicate secondary support tools such as
|
||||||
by those not familiar with Python. The proposed syntax can be
|
language-sensitive editors and other "`toy parser tools out
|
||||||
generalized as follows:
|
there`_"
|
||||||
|
|
||||||
'def' NAME '(' PARAMETERS ')' ['as' DECORATORS] ':'
|
.. _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
|
||||||
|
|
||||||
where DECORATORS is a comma-separated list of expressions,
|
Proposed Syntax
|
||||||
or a tuple. Using the latter form, the last example above
|
===============
|
||||||
would look like:
|
|
||||||
|
|
||||||
def foo(self) as (synchronized(lock), classmethod):
|
The currently proposed syntax is::
|
||||||
perform method operation
|
|
||||||
|
|
||||||
This form make is possible for the list of decorators to
|
def func(arg1, arg2, ...) [dec1, dec2, ...]:
|
||||||
span multiple lines without using the line continuation operator.
|
pass
|
||||||
|
|
||||||
Alternate Syntaxes
|
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.
|
||||||
|
|
||||||
Other syntaxes have been proposed in comp.lang.python and
|
Alternate Proposals
|
||||||
python-dev. Unfortunately, no one syntax has come out as a clear
|
===================
|
||||||
winner in the lengthy discussions. The most common suggestions
|
|
||||||
are demonstrated below. The proposed syntax is also included
|
|
||||||
for easy comparison.
|
|
||||||
|
|
||||||
Proposed Syntax
|
A few other syntaxes have been proposed::
|
||||||
|
|
||||||
def foo(self) as synchronized(lock), classmethod:
|
def func(arg1, arg2, ...) as dec1, dec2, ...:
|
||||||
perform method operation
|
pass
|
||||||
|
|
||||||
def foo(self) as (synchronized(lock), classmethod):
|
The absence of brackets makes it cumbersome to break long lists of
|
||||||
perform method operation
|
decorators across multiple lines. The keyword "as" doesn't have the
|
||||||
|
same meaning as its use in the ``import`` statement.
|
||||||
|
|
||||||
Prefix Forms
|
::
|
||||||
|
|
||||||
def [synchronized(lock), classmethod] foo(self):
|
def [dec1, dec2, ...] func(arg1, arg2, ...):
|
||||||
perform method operation
|
pass
|
||||||
|
|
||||||
def synchronized(lock), classmethod foo(self):
|
This form has the disadvantage that the decorators become visually
|
||||||
perform method operation
|
higher priority than the function name and argument list.
|
||||||
|
|
||||||
# Same as above, but only identifiers are allowed
|
::
|
||||||
sync = synchronized(lock)
|
|
||||||
def sync, classmethod foo(self):
|
|
||||||
perform method operation
|
|
||||||
|
|
||||||
# Java-like
|
def func [dec1, dec2, ...] (arg1, arg2, ...):
|
||||||
sync = synchronized(lock)
|
pass
|
||||||
def @sync @classmethod foo(self):
|
|
||||||
perform method operation
|
|
||||||
|
|
||||||
Postfix Forms
|
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.
|
||||||
|
|
||||||
def foo(self) [synchronized(lock), classmethod]:
|
::
|
||||||
perform method operation
|
|
||||||
|
|
||||||
def foo(self) (synchronized(lock), classmethod):
|
using:
|
||||||
perform method operation
|
dec1
|
||||||
|
dec2
|
||||||
|
...
|
||||||
|
def foo(arg1, arg2, ...):
|
||||||
|
pass
|
||||||
|
|
||||||
def foo(self) {'pre': synchronized(lock), 'classmethod': True}:
|
The function definition is not nested within the using: block making
|
||||||
perform method operation
|
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.
|
||||||
|
|
||||||
I'm not as fond of the forms that use '[ ]' since code like
|
Current Implementation
|
||||||
'foo()[a]' looks as if you are getting the item 'a' from 'foo()'.
|
======================
|
||||||
Although, this isn't as much of an issue when using '[ ]' in
|
|
||||||
a prefix form. The Java-like syntax adds new syntax that is
|
|
||||||
very arbitrary and is almost Perl-ish. In addition, since the
|
|
||||||
order in which the decorators are applied may matter, the last,
|
|
||||||
dictionary-style, syntax must be eliminated.
|
|
||||||
|
|
||||||
Implementation Issues
|
|
||||||
|
|
||||||
In the following example there are two function decorators:
|
Michael Hudson has posted a `patch`_ at Starship, which implements the
|
||||||
synchronized(lock) and classmethod.
|
proposed syntax and left-first application of decorators::
|
||||||
|
|
||||||
def foo(self) as synchronized(lock), classmethod:
|
def func(arg1, arg2, ...) [dec1, dec2]:
|
||||||
perform method operation
|
pass
|
||||||
|
|
||||||
Since these all appear within the operation of the 'def'
|
is equivalent to::
|
||||||
itself, it makes sense that synchronized, lock, and
|
|
||||||
classmethod must exist at the time that the definition
|
|
||||||
is executed. In addition, each of these arguments will be
|
|
||||||
evaluated before being applied to the compiled function.
|
|
||||||
This means that arguments like synchronized(lock) must
|
|
||||||
return a descriptor that will be applied to foo. Therefore,
|
|
||||||
the code above translates to:
|
|
||||||
|
|
||||||
def foo(self):
|
def func(arg1, arg2, ...):
|
||||||
perform method operation
|
pass
|
||||||
foo = classmethod(<returned-descriptor>(foo))
|
func = dec2(dec1(func))
|
||||||
|
|
||||||
In the example above, <returned-descriptor> refers to the
|
|
||||||
descriptor returned by evaluating synchronized(lock).
|
|
||||||
|
|
||||||
It could easily be argued that the descriptors should be applied
|
though without the intermediate creation of a variable named ``func``.
|
||||||
in reverse order to make the application of the descriptor look
|
|
||||||
more like the resultant code. I tend to prefer this form.
|
|
||||||
|
|
||||||
def foo(self):
|
.. _patch: http://starship.python.net/crew/mwh/hacks/meth-syntax-sugar.diff
|
||||||
perform method operation
|
|
||||||
foo = <returned-descriptor>(classmethod(foo))
|
|
||||||
|
|
||||||
In either case, the modified function is bound to the function
|
Examples
|
||||||
name when the 'def' statement is executed.
|
========
|
||||||
|
|
||||||
Open Issues
|
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.
|
||||||
|
|
||||||
It is not clear at the moment if it is even possible to have
|
1. Define a function to be executed at exit. Note that the function
|
||||||
multiple decorators for a function. If decorators are required
|
isn't actually "wrapped" in the usual sense.
|
||||||
to take a function/method and return a descriptor, it might
|
|
||||||
not even be possible to wrap multiple decorators. This should
|
|
||||||
be explored since the best syntax for multiple decorators
|
|
||||||
may not be the same as the best syntax for a single decorator.
|
|
||||||
|
|
||||||
Current Implementations
|
::
|
||||||
|
|
||||||
I am not personally familiar enough with Python's source to
|
def onexit(f):
|
||||||
implement the proposed syntax; however, Michael Hudson has
|
import atexit
|
||||||
implemented the "square-bracketed" syntax (see patch at
|
atexit.register(f)
|
||||||
http://starship.python.net/crew/mwh/hacks/meth-syntax-sugar.diff).
|
return f
|
||||||
It should be fairly simple for the Python development team
|
|
||||||
to translate this patch to the proposed syntax.
|
|
||||||
|
|
||||||
|
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
|
Possible Extensions
|
||||||
|
===================
|
||||||
|
|
||||||
The proposed syntax is general enough that it could be used
|
The proposed syntax is general enough that it could be used on class
|
||||||
on class definitions as well as shown below.
|
definitions as well::
|
||||||
|
|
||||||
class foo(object) as classmodifier:
|
class foo(object) [dec1, dec2, ...]:
|
||||||
class definition here
|
class definition here
|
||||||
|
|
||||||
However, there are no obvious parallels for use with other
|
Use would likely be much less than function decorators. The current
|
||||||
descriptors such as property().
|
patch only implements function decorators.
|
||||||
|
|
||||||
|
|
||||||
Conclusion
|
|
||||||
|
|
||||||
The current method of translating an instance method to a class
|
|
||||||
or static method is awkward. A new syntax for applying function
|
|
||||||
decorators should be implemented (proposed syntax shown below).
|
|
||||||
|
|
||||||
def foo(self) as synchronized(lock), classmethod:
|
|
||||||
perform method operation
|
|
||||||
|
|
||||||
The proposed syntax is simple, powerful, easy to read, and
|
|
||||||
therefore preserves those qualities of the Python language.
|
|
||||||
|
|
||||||
|
|
||||||
Copyright
|
Copyright
|
||||||
|
=========
|
||||||
|
|
||||||
This document has been placed in the public domain.
|
This document has been placed in the public domain.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue