From b2436faf3bb01b5e4af0d1a3f35d475b6df723d8 Mon Sep 17 00:00:00 2001 From: Skip Montanaro Date: Tue, 23 Mar 2004 16:41:17 +0000 Subject: [PATCH] Checking in what we have so far. Still digesting some of Jim Jewett's inputs. --- pep-0318.txt | 374 ++++++++++++++++++++++++++++----------------------- 1 file changed, 206 insertions(+), 168 deletions(-) diff --git a/pep-0318.txt b/pep-0318.txt index 04cd6c866..9af8dbcdb 100644 --- a/pep-0318.txt +++ b/pep-0318.txt @@ -2,243 +2,281 @@ PEP: 318 Title: Function/Method Decorator Syntax Version: $Revision$ Last-Modified: $Date$ -Author: Kevin D. Smith +Author: Kevin D. Smith , + Jim Jewett , + Skip Montanaro 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) + 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 + def foo(cls): + 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 - an instance method to a class/static method is illustrated in the - code below. +Background +========== - def classmethod foo(self): - perform method operation +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`_. - 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): - 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: +Design Goals +============ - def protected classmethod foo(self): - perform method operation +The new syntax should - 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): - perform method operation - foo = protected(classmethod(foo)) +* work with multiple wrappers per definition - 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. +* 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 - def synchronized(lock) classmethod foo(self): - perform method operation +* not make future extensions more difficult - 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. +* be easy to type; programs that use it are expected to use it very + frequently - def foo(self) as synchronized(lock), classmethod: - perform method operation +* 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 - This syntax is quite clear and could probably be interpreted - by those not familiar with Python. The proposed syntax can be - generalized as follows: +* not needlessly complicate secondary support tools such as + language-sensitive editors and other "`toy parser tools out + 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, - or a tuple. Using the latter form, the last example above - would look like: +Proposed Syntax +=============== - def foo(self) as (synchronized(lock), classmethod): - perform method operation +The currently proposed syntax is:: - This form make is possible for the list of decorators to - span multiple lines without using the line continuation operator. + def func(arg1, arg2, ...) [dec1, dec2, ...]: + 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 - 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. +Alternate Proposals +=================== - Proposed Syntax +A few other syntaxes have been proposed:: - def foo(self) as synchronized(lock), classmethod: - perform method operation + def func(arg1, arg2, ...) as dec1, dec2, ...: + pass - def foo(self) as (synchronized(lock), classmethod): - 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. - Prefix Forms +:: - def [synchronized(lock), classmethod] foo(self): - perform method operation + def [dec1, dec2, ...] func(arg1, arg2, ...): + pass - def synchronized(lock), classmethod foo(self): - perform method operation +This form has the disadvantage that the decorators become visually +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 - sync = synchronized(lock) - def @sync @classmethod foo(self): - perform method operation + def func [dec1, dec2, ...] (arg1, arg2, ...): + pass - 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): - perform method operation + using: + dec1 + dec2 + ... + def foo(arg1, arg2, ...): + pass - def foo(self) {'pre': synchronized(lock), 'classmethod': True}: - perform method operation +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. - 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. - -Implementation Issues +Current Implementation +====================== - In the following example there are two function decorators: - synchronized(lock) and classmethod. +Michael Hudson has posted a `patch`_ at Starship, which implements the +proposed syntax and left-first application of decorators:: - def foo(self) as synchronized(lock), classmethod: - perform method operation + def func(arg1, arg2, ...) [dec1, dec2]: + 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: +is equivalent to:: - def foo(self): - perform method operation - foo = classmethod((foo)) - - In the example above, refers to the - descriptor returned by evaluating synchronized(lock). + def func(arg1, arg2, ...): + pass + func = dec2(dec1(func)) - 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. +though without the intermediate creation of a variable named ``func``. - def foo(self): - perform method operation - foo = (classmethod(foo)) +.. _patch: http://starship.python.net/crew/mwh/hacks/meth-syntax-sugar.diff - In either case, the modified function is bound to the function - name when the 'def' statement is executed. +Examples +======== -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 - 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. +1. Define a function to be executed at exit. Note that the function + isn't actually "wrapped" in the usual sense. -Current Implementations +:: - 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. + 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 definition here + 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. +This document has been placed in the public domain.