Checking in what we have so far. Still digesting some of Jim Jewett's
inputs.
This commit is contained in:
parent
efebd8e76c
commit
b2436faf3b
366
pep-0318.txt
366
pep-0318.txt
|
@ -2,241 +2,279 @@ PEP: 318
|
|||
Title: Function/Method Decorator Syntax
|
||||
Version: $Revision$
|
||||
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
|
||||
Type: Standards Track
|
||||
Content-Type: text/plain
|
||||
Content-Type: text/x-rst
|
||||
Created: 05-Jun-2003
|
||||
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
|
||||
========
|
||||
|
||||
The current method for declaring class and static methods
|
||||
is awkward and can lead to code that is difficult to understand.
|
||||
This PEP introduces possible new syntax which will place the
|
||||
translation of instance methods to class/static methods at
|
||||
the same point in the code as the method's declaration.
|
||||
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 translating an instance method into a
|
||||
class/static method places the actual translation at a different
|
||||
point in the code than the declaration of the method. The
|
||||
code below demonstrates this.
|
||||
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)
|
||||
|
||||
When the method is very short, it is easy to look ahead and see
|
||||
that this is a class method. However, if the method is more than
|
||||
15 lines or so, the translation into a class method is not
|
||||
obvious. A solution to this problem is to move the translation
|
||||
of the method to the same point as the method's declaration.
|
||||
The proposed syntax, shown in the example below, is discussed
|
||||
in the following sections.
|
||||
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(self) as synchronized(lock), classmethod:
|
||||
perform method operation
|
||||
|
||||
|
||||
Proposal
|
||||
|
||||
Probably the simplest way to place the decorator that translates
|
||||
an instance method to a class/static method is illustrated in the
|
||||
code below.
|
||||
|
||||
def classmethod foo(self):
|
||||
perform method operation
|
||||
|
||||
The code in this example will simply perform the following.
|
||||
|
||||
def foo(self):
|
||||
perform method operation
|
||||
def foo(cls):
|
||||
pass
|
||||
foo = synchronized(lock)(foo)
|
||||
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:
|
||||
with an alternative that places the decoration in the function's
|
||||
declaration::
|
||||
|
||||
def protected classmethod foo(self):
|
||||
perform method operation
|
||||
def foo(cls) using [synchronized(lock), classmethod]:
|
||||
pass
|
||||
|
||||
which would be equivalent to the current form:
|
||||
Background
|
||||
==========
|
||||
|
||||
def foo(self):
|
||||
perform method operation
|
||||
foo = protected(classmethod(foo))
|
||||
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`_.
|
||||
|
||||
While this syntax is simple and easy to read, it does become
|
||||
cluttered and more obscure if you wish to allow arguments to be
|
||||
sent to the function decorator.
|
||||
.. _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 synchronized(lock) classmethod foo(self):
|
||||
perform method operation
|
||||
Design Goals
|
||||
============
|
||||
|
||||
Instead of placing the decorators in front of the function name,
|
||||
a better place might be after it, as shown below. The word 'as' is
|
||||
added simply as a separator to assist in readability.
|
||||
The new syntax should
|
||||
|
||||
def foo(self) as synchronized(lock), classmethod:
|
||||
perform method operation
|
||||
* work for arbitrary wrappers, including user-defined callables and
|
||||
the existing builtins ``classmethod()`` and ``staticmethod``
|
||||
|
||||
This syntax is quite clear and could probably be interpreted
|
||||
by those not familiar with Python. The proposed syntax can be
|
||||
generalized as follows:
|
||||
* work with multiple wrappers per definition
|
||||
|
||||
'def' NAME '(' PARAMETERS ')' ['as' DECORATORS] ':'
|
||||
* 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
|
||||
|
||||
where DECORATORS is a comma-separated list of expressions,
|
||||
or a tuple. Using the latter form, the last example above
|
||||
would look like:
|
||||
* not make future extensions more difficult
|
||||
|
||||
def foo(self) as (synchronized(lock), classmethod):
|
||||
perform method operation
|
||||
* be easy to type; programs that use it are expected to use it very
|
||||
frequently
|
||||
|
||||
This form make is possible for the list of decorators to
|
||||
span multiple lines without using the line continuation operator.
|
||||
* 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
|
||||
|
||||
Alternate Syntaxes
|
||||
* not needlessly complicate secondary support tools such as
|
||||
language-sensitive editors and other "`toy parser tools out
|
||||
there`_"
|
||||
|
||||
Other syntaxes have been proposed in comp.lang.python and
|
||||
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.
|
||||
.. _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
|
||||
===============
|
||||
|
||||
def foo(self) as synchronized(lock), classmethod:
|
||||
perform method operation
|
||||
The currently proposed syntax is::
|
||||
|
||||
def foo(self) as (synchronized(lock), classmethod):
|
||||
perform method operation
|
||||
def func(arg1, arg2, ...) [dec1, dec2, ...]:
|
||||
pass
|
||||
|
||||
Prefix Forms
|
||||
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.
|
||||
|
||||
def [synchronized(lock), classmethod] foo(self):
|
||||
perform method operation
|
||||
Alternate Proposals
|
||||
===================
|
||||
|
||||
def synchronized(lock), classmethod foo(self):
|
||||
perform method operation
|
||||
A few other syntaxes have been proposed::
|
||||
|
||||
# Same as above, but only identifiers are allowed
|
||||
sync = synchronized(lock)
|
||||
def sync, classmethod foo(self):
|
||||
perform method operation
|
||||
def func(arg1, arg2, ...) as dec1, dec2, ...:
|
||||
pass
|
||||
|
||||
# Java-like
|
||||
sync = synchronized(lock)
|
||||
def @sync @classmethod foo(self):
|
||||
perform method operation
|
||||
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.
|
||||
|
||||
Postfix Forms
|
||||
::
|
||||
|
||||
def foo(self) [synchronized(lock), classmethod]:
|
||||
perform method operation
|
||||
def [dec1, dec2, ...] func(arg1, arg2, ...):
|
||||
pass
|
||||
|
||||
def foo(self) (synchronized(lock), classmethod):
|
||||
perform method operation
|
||||
This form has the disadvantage that the decorators become visually
|
||||
higher priority than the function name and argument list.
|
||||
|
||||
def foo(self) {'pre': synchronized(lock), 'classmethod': True}:
|
||||
perform method operation
|
||||
::
|
||||
|
||||
I'm not as fond of the forms that use '[ ]' since code like
|
||||
'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.
|
||||
def func [dec1, dec2, ...] (arg1, arg2, ...):
|
||||
pass
|
||||
|
||||
Implementation Issues
|
||||
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.
|
||||
|
||||
In the following example there are two function decorators:
|
||||
synchronized(lock) and classmethod.
|
||||
::
|
||||
|
||||
def foo(self) as synchronized(lock), classmethod:
|
||||
perform method operation
|
||||
using:
|
||||
dec1
|
||||
dec2
|
||||
...
|
||||
def foo(arg1, arg2, ...):
|
||||
pass
|
||||
|
||||
Since these all appear within the operation of the 'def'
|
||||
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:
|
||||
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.
|
||||
|
||||
def foo(self):
|
||||
perform method operation
|
||||
foo = classmethod(<returned-descriptor>(foo))
|
||||
Current Implementation
|
||||
======================
|
||||
|
||||
In the example above, <returned-descriptor> refers to the
|
||||
descriptor returned by evaluating synchronized(lock).
|
||||
Michael Hudson has posted a `patch`_ at Starship, which implements the
|
||||
proposed syntax and left-first application of decorators::
|
||||
|
||||
It could easily be argued that the descriptors should be applied
|
||||
in reverse order to make the application of the descriptor look
|
||||
more like the resultant code. I tend to prefer this form.
|
||||
def func(arg1, arg2, ...) [dec1, dec2]:
|
||||
pass
|
||||
|
||||
def foo(self):
|
||||
perform method operation
|
||||
foo = <returned-descriptor>(classmethod(foo))
|
||||
is equivalent to::
|
||||
|
||||
In either case, the modified function is bound to the function
|
||||
name when the 'def' statement is executed.
|
||||
def func(arg1, arg2, ...):
|
||||
pass
|
||||
func = dec2(dec1(func))
|
||||
|
||||
Open Issues
|
||||
though without the intermediate creation of a variable named ``func``.
|
||||
|
||||
It is not clear at the moment if it is even possible to have
|
||||
multiple decorators for a function. If decorators are required
|
||||
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.
|
||||
.. _patch: http://starship.python.net/crew/mwh/hacks/meth-syntax-sugar.diff
|
||||
|
||||
Current Implementations
|
||||
Examples
|
||||
========
|
||||
|
||||
I am not personally familiar enough with Python's source to
|
||||
implement the proposed syntax; however, Michael Hudson has
|
||||
implemented the "square-bracketed" syntax (see patch at
|
||||
http://starship.python.net/crew/mwh/hacks/meth-syntax-sugar.diff).
|
||||
It should be fairly simple for the Python development team
|
||||
to translate this patch to the proposed syntax.
|
||||
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 as shown below.
|
||||
The proposed syntax is general enough that it could be used on class
|
||||
definitions as well::
|
||||
|
||||
class foo(object) as classmodifier:
|
||||
class foo(object) [dec1, dec2, ...]:
|
||||
class definition here
|
||||
|
||||
However, there are no obvious parallels for use with other
|
||||
descriptors such as property().
|
||||
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
|
|
Loading…
Reference in New Issue