Checking in what we have so far. Still digesting some of Jim Jewett's

inputs.
This commit is contained in:
Skip Montanaro 2004-03-23 16:41:17 +00:00
parent efebd8e76c
commit b2436faf3b
1 changed files with 206 additions and 168 deletions

View File

@ -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.