1063
Comment:
|
27429
Added a few pros and cons to 5.A (pie decorator syntax).
|
Deletions are marked like this. | Additions are marked like this. |
Line 1: | Line 1: |
Line 14: | Line 13: |
The winning syntax as of now uses the '@' symbol. There has been a long discussion about the syntax to use for decorators in Python. See for example these threads: | The winning syntax as of now uses the '@' symbol, as described in [http://mail.python.org/pipermail/python-dev/2004-June/045516.html this message]. Mark Russell implemented this version. [http://mail.python.org/pipermail/patches/2004-July/015452.html Here] is the message describing the patch he checked in. There has been a long discussion about the syntax to use for decorators in Python. See for example these threads: |
Line 22: | Line 23: |
!#python | #!python |
Line 34: | Line 35: |
== Current Decorator Proposals == After the @decorator syntax was "accepted", lots of people threw up alarms and a huge series of threads started exploding on Python-dev. Here are the current alternatives that I could find that are being argued, with pros and cons: I give two examples that might be common uses in the future. Classmethod declarations, and something like static typing (adapters), declaring what type parameters a function expects and returns. '''A. pie decorator syntax''' {{{ @classmethod def foo(arg1,arg2): ... @accepts(int,int) @returns(float) def bar(low,high): ... }}} * + Implementation already exists (and is in Python 2.4a2) * + Java-like, so not completely unknown to everyone. * + Makes the syntax obvious visually (i.e., obviously not a normal statement) * + Will not be silently ignored * + Compile-time * + One decorator per line * + Separate from the def syntax (desired by some for making decoration stand out and keeping def the same) * + Currently not used in Python so @decorators can be inserted anywhere, such as after parameters or inside expressions. * + The @ special character will make syntax highlighting easier than it would be for normal statements in "magic" locations. * - Separate from the def syntax (undesired by some for simple decorators like classmethod/staticmethod) * - Ugly? * - Introduces a new character in the language * - The @ special character is used in IPython and Leo * - Punctuation-based syntax raises Perlfears. But no "obvious" keyword or expression has been suggested. * - Because of no indentation, looks confusing when definitions are not separated by empty lines. But adding empty lines makes it hard to determine where the function definition truly starts. * - That's the first case in Python where a line following another one with the same identation has a meaning. * - Breaks in interactive shell * - Comes before the def keyword. Supercedes the function declaration itself, thus applying modifications implicitly to something not yet defined. * - @staticmethod may look like staticmethod is not a defined variable, but a compile time option * - Requires the programmer to scan an arbitrary distance from the def in *both* directions to see everything of interest (arg, decs, etc.). * - Cannot be "folded" by editors as easily, as an arbitrary number of prefix lines should be folded with the body (otherwise, folding becomes less useful). * 0 The @ character is often used (in other languages) to mean "attribute". For annotations, this is good. For more active decorators, it may not be so good. FWIW, here is Guido's jumble example in this syntax. {{{ class C(object): @staticmethod @funcattrs(grammar="'@' dotted_name [ '(' [arglist] ')' ]", status="experimental", author="BDFL") def longMethodNameForEffect(longArgumentOne=None, longArgumentTwo=42): """This method blah, blah. It supports the following arguments: - longArgumentOne -- a string giving ... - longArgumentTwo -- a number giving ... blah, blah. """ raise NotYetImplemented }}} And here is an example taken from the current test_decorators.py. This exposes the problem of using two lines together with some meaning but without identation. {{{ class TestDecorators(unittest.TestCase): ... def test_dotted(self): decorators = MiscDecorators() @decorators.author('Cleese') def foo(): return 42 self.assertEqual(foo(), 42) self.assertEqual(foo.author, 'Cleese') }}} '''B. list-before-def syntax''' {{{ [classmethod] def foo(arg1,arg2): ... [accepts(int,int), returns(float)] def bar(low,high): ... }}} * + Implementation already exists * + C# like * + Can be made backwards compatible-ish, with a "hack" * + Doesn't cause breakage in existing code-searching tools * + Use visually existing syntax * - Doesn't cause breakage in existing code-searching tools * - Would not work in interactive mode (list would be interpreted right away) * - EuroPython didn't like it ( why? ) (hard to teach newbies about the magic) * - The backwards compatability wouldn't be portable to Jython * - Looks like a normal expression, but has "magic" behavior of altering a function object * - Breaks in interactive shell * - Harder to highlight (looks like a normal list) '''C1. list-after-def syntax''' {{{ def foo(arg1,arg2) [classmethod]: ... def bar(low,high) [accepts(int,int), returns(float)]: ... }}} * + Implementation already exists * + Also somewhat C#-like * + Was a "community favorite" at one time * + Clearly a part of function declaration * + Looks ok for simple decorators such as classmethod * + Won't break simplistic code analyzers or grep for function def * + Use visually existing syntax * + Allows one-liner definitions * - Long lists of decorators/arguments cause ugly line wraps * - Little to distinguish it visually from argument list * - or 0 For long argument list, decorators are very far from def. ''I see being too close to def as a minus.'' * - Guido hates it because it hides crucial information after the signature, it's easy to miss the transition between a long argument list and a long decorator list, and it's cumbersom to cut and paste a decorator list for reuse. * - Creates another meaning for []. Since it does so inside the function definition, it will be a distraction for beginners. * 0 Brackets are used in other fields to indicate an annotation of some sort (but in Python, it doesn't) I don't see why longs lists of decorators are an issue with this syntax. Consider the following example: {{{ def foo(arg1, arg2) [ complicated(manyArgs=1, notTooUgly='yes'), even_more_complicated(42)]: ... }}} That doesn't look particularly ugly to me. --- It also isn't very long. Here is an [http://mail.python.org/pipermail/python-dev/2004-August/047112.html example Guido just sent to python-dev]: {{{ class C(object): def longMethodNameForEffect(longArgumentOne=None, longArgumentTwo=42) [ staticmethod, funcattrs(grammar="'@' dotted_name [ '(' [arglist] ')' ]", status="experimental", author="BDFL") ]: """This method blah, blah. It supports the following arguments: - longArgumentOne -- a string giving ... - longArgumentTwo -- a number giving ... blah, blah. """ raise NotYetImplemented }}} And he editorializes: {{{ That's a total jumble of stuff ending with a smiley. (True story: I left out the colon when typing up this example and only noticed in proofing.) Problems with this form: - it hides crucial information (e.g. that it is a static method) after the signature, where it is easily missed - it's easy to miss the transition between a long argument list and a long decorator list - it's cumbersome to cut and paste a decorator list for reuse, because it starts and ends in the middle of a line Given that the whole point of adding decorator syntax is to move the decorator from the end ("foo = staticmethod(foo)" after a 100-line body) to the front, where it is more in-your-face, it should IMO be moved all the way to the front. }}} '''C2. list-after-def syntax with a (pseudo-)keyword''' {{{ def foo(arg1,arg2) using [classmethod]: ... def bar(low,high) using [accepts(int,int), returns(float)]: ... }}} This combines C1 with a keyword; it general, it has all the advantages of either, so I will only list those that are unique to the combination. * + Groups the decorators with a list, but explains what they are doing, so the list no longer has a magical new meaning. * + Easily extended; No special characters are "used up", and in the future, other pseudo-keywords could be added. * + The pseudo-keyword makes it easier to see the separation between the argument tuple and the decorator list. * + The pseudo-keyword can act as an implicit line-continuation, which helps with (but does not solve) the midline problem. * - Some proponents objected to adding a keyword, because of more typing. * 0 Makes the decoration look like a second-class or optional part of the definition. This is true, but may have caused some emotional objection. * - There was very little agreement on which word should be used. * - No implementation currently exists (it ''may'' be a simple variation of the implementation of C1). FWIW, here is Guido's jumble example in this syntax. {{{ class C(object): def longMethodNameForEffect(longArgumentOne=None, longArgumentTwo=42) using [staticmethod, funcattrs(grammar="'@' dotted_name [ '(' [arglist] ')' ]", status="experimental", author="BDFL")]: """This method blah, blah. It supports the following arguments: - longArgumentOne -- a string giving ... - longArgumentTwo -- a number giving ... blah, blah. """ raise NotYetImplemented }}} Without the pseudo-keyword acting as line continuation it reads : {{{ class C(object): def longMethodNameForEffect(longArgumentOne=None, longArgumentTwo=42) using [ staticmethod, funcattrs(grammar="'@' dotted_name [ '(' [arglist] ')' ]", status="experimental", author="BDFL")]: """This method blah, blah. It supports the following arguments: - longArgumentOne -- a string giving ... - longArgumentTwo -- a number giving ... blah, blah. """ raise NotYetImplemented }}} which feel is more consistent with the rest of python parsing-wise, without decreasing readability... '''C3. tuple-after-def syntax with a (pseudo-)keyword''' {{{ def foo(arg1,arg2) using classmethod,: ... def bar(low,high) using accepts(int,int), returns(float): ... class C(object): def longMethodNameForEffect(longArgumentOne=None, longArgumentTwo=42) using ( staticmethod, funcattrs(grammar="'@' dotted_name [ '(' [arglist] ')' ]", status="experimental", author="BDFL")): """This method blah, blah. It supports the following arguments: - longArgumentOne -- a string giving ... - longArgumentTwo -- a number giving ... blah, blah. """ raise NotYetImplemented }}} Very similar to C2, but with those slight differences * + Nicer for one-line decoration when using multiple decorators. * - The hanging comma feel strange for 1-element tuple, and will probably often been forgotten. * - Decorator and arguments looks (too?) similar for multiline case. * - No implementation currently exists. The 1st drawback could be removed if one allows both tuple and single-element after the pseudo-keyword, trading consistency for readability and convenience. '''C4. tuple-after-def syntax with a % operator''' {{{ def foo(arg1,arg2) % classmethod: ... def bar(low,high) % accepts(int,int), returns(float): ... class C(object): def longMethodNameForEffect(longArgumentOne=None, longArgumentTwo=42) # make implicit linebreak possible here % (staticmethod, funcattrs(grammar="'@' dotted_name [ '(' [arglist] ')' ]", status="experimental", author="BDFL")): """This method blah, blah. It supports the following arguments: - longArgumentOne -- a string giving ... - longArgumentTwo -- a number giving ... blah, blah. """ raise NotYetImplemented # this is also possible for consistency: foo %= classmethod bar %= (accepts(int,int), returns(float)) }}} Very similar to C3, but with those slight differences * + Nicer for one-line decoration when using multiple decorators. * + Similar to use of % in string formatting operation * - Decorator and arguments looks (too?) similar for multiline case. * - No implementation currently exists. One more point: % could also be used in chained fashion: {{{ bar = bar % accepts(int,int) % returns(float) }}} (making it similar to E2 below) '''D. list at top of function body syntax''' {{{ def foo(arg1,arg2): [classmethod] ... def bar(low,high): [accepts(int,int), returns(float)] ... }}} * + Also somewhat C#-like * + Consistent with how docstrings are used. * + Looks ok for simple or complex decorators * + Won't break simplistic code analyzers or grep for function def * + Solves line wrap problem with above proposal * 0 There is a hack that implements this now [http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/286147 here]. * - Guido's europython presentation said this didn't win out, but not why * - Adds 'magic' behavior to a normal python expression (lists). Not exactly true: there is nothing magic in string when it's used in docstring - it's a normal string in the "magic" place. * - Compatibility issue: program that is working under 2.4 will not work properly under earlier versions without any explanation (old syntax compatible decorators will not blow in your face) * 0 Perhaps decorators should be allowed before or after the docstring. If you have to choose, I'd choose making it before the docstring. * - No implementation currently exists. * - Guido ruled out any solution involving special syntax inside the block, because "you shouldn't have to peek inside the block to find out important external properties of the function." [http://mail.python.org/pipermail/python-dev/2004-August/047279.html] '''E1. pie decorator at top of function body syntax''' {{{ def foo(arg1,arg2): @classmethod ... def bar(low,high): @accepts(int,int) @returns(float) ... }}} * Same as above but with pie syntax. * Could use @doc too as a docstring alternative * - No implementation currently exists. * - Guido ruled out any solution involving special syntax inside the block, because "you shouldn't have to peek inside the block to find out important external properties of the function." [http://mail.python.org/pipermail/python-dev/2004-August/047279.html] '''E2. vbar decorator at top of function body syntax''' {{{ def foo(arg1,arg2): |classmethod ... def bar(low,high): |accepts(int,int) |returns(float) ... def longMethodNameForEffect(longArgumentOne=None, longArgumentTwo=42): |staticmethod |funcattrs(grammar="'@' dotted_name [ '(' [arglist] ')' ]", status="experimental", author="BDFL") """This method blah, blah. It supports the following arguments: - longArgumentOne -- a string giving ... - longArgumentTwo -- a number giving ... blah, blah. """ }}} * + Consistent with how docstrings are used. * + Looks ok for simple or complex decorators * + Won't break simplistic code analyzers or grep for function def * + Vertical bars visually "attach" the decorators to the name. * + Decorators are indented, so it's clear that they modify the function. * - Indented decorators look like they should be evaluated when the function is called, not when the function is parsed. I have the same objection to docstrings, though. * + Reads naturally as "pipe" operator to unix hackers (which is semantically correct, since the defined function gets passed through the decorators, one at a time, and the result is used. * - Misleading to Unix hackers, since the order of evaluation is "backwards". * + Doesn't use up one of the currently unused characters (such as "@") -- it's always possible that we'll find another good use for those later. * - For some fonts, "|" looks similar to "I" or "l" or "1". Some think that code highlighting would remove this problem, but with normal-sized fonts, there are not many pixels in it to show a color clearly. "@" has a big blob of pixels, and is very distinct. * - The key with "|" on it is often in an awkward location on laptop keyboards * 0 Perhaps decorators should be allowed before or after the docstring. If you have to choose, I'd choose making it before the docstring. * - No implementation currently exists. * - When using just one bar, it isn't noticable enough. It seems more a part of the actual decorator function name than something demarking the function. * - When using multiple decorators, the pattern formed by the vertical bars draws the eyes too much and makes it hard to focus on the signature. * - Guido ruled out any solution involving special syntax inside the block, because "you shouldn't have to peek inside the block to find out important external properties of the function." [http://mail.python.org/pipermail/python-dev/2004-August/047279.html] '''E3. vbar decorator after arg''' {{{ def longMethodNameForEffect(longArgumentOne=None, longArgumentTwo=42) |staticmethod |funcattrs(grammar="'@' dotted_name [ '(' [arglist] ')' ]", status="experimental", author="BDFL"): """This method blah, blah. It supports the following arguments: - longArgumentOne -- a string giving ... - longArgumentTwo -- a number giving ... blah, blah. """ def bar(low,high) |accepts(int,int) |returns(float): ... def foo(arg1,arg2) | classmethod: ... }}} An alternative (inspired by a typing error I corrected in E2 in the Guido example) would be to put vbar decorator before the colon... Basically it has the same characteristic than E2, with the following slight differences: * + More obviously attached to function definition * + Possible 1-line version as readable as the inline syntax (see F.) for short function/decoration * - More difficult to parse? * - New syntax having no equivalent in other part of python (but all @ / | propositions suffer from that ) * - No implementation currently exists. '''F. inline syntax''' {{{ def classmethod foo(arg1,arg2): ... ? }}} * + Simple * + More readable/natural * + Obviousely attached to the function * - Does not allow for arguments to the decorator inline, or multiple decorators * - The natural place where everyone looks for the function name now is a possible container for other information * - Complicates things like colorization and other functions of helper tools * - Many people don't like the idea of having something between 'def' and the function name * - Breaks etags (couldn't it be fixed?) * - No implementation currently exists. '''G. as decorator''' {{{ as classmethod def foo(arg1,arg2): ... ? }}} * + Non-punctuation based * + Does not use an existing mechanism with 'magic' behavior * - Guido specifically vetos: "as" means "rename" in too many logical, common places that are * - No implementation currently exists. '''H. pie decorator using a different character''' For example, using the '|' character: {{{ |classmethod def foo(arg1,arg2): ... |accepts(int,int) |returns(float) def bar(low,high): ... }}} Same pros and cons as @decorator, but additionally: * + Likely to be a trivial change to what is already in 2.4a2. * + It doesn't break Leo, IPython, or any other tool that uses @ as a special character. * + The association with pipes makes some sense: "take this thing and pass it through that thing to get a modified thing". * - Most characters (including pipe) already have a meaning. Ending an expression at the linebreak will probably keep these from becoming ambiguous, but ... that gets fragile. ----- /!\ Edit conflict! Other version: ----- * - Misleading to Unix hackers, since the order of evaluation is "backwards". * - For some fonts, "|" looks similar to "I" or "l" or "1" Some think that code highlighting would remove this problem, but with normal-sized fonts, there are not many pixels in it to show a color clearly. "@" has a big blob of pixels, and is very distinct. ----- /!\ Edit conflict! Your version: ----- ----- /!\ End of edit conflict ----- ----- /!\ Edit conflict! Other version: ----- ----- /!\ Edit conflict! Your version: ----- * + The association with pipes makes some sense: "take this thing and pass it through that thing to get a modified thing". * - Most characters (including pipe) already have a meaning. Ending an expression at the linebreak will probably keep these from becoming ambiguous, but ... that gets fragile. * - For some fonts, "|" looks similar to "I" or "l"; but code highlighting would remove this problem. ----- /!\ End of edit conflict ----- * - The key with "|" on it is often in an awkward location on laptop keyboards * - When using just one bar, it isn't noticable enough. It seems more a part of the actual decorator function name than something demarking the function. * - When using multiple decorators, the pattern formed by the vertical bars draws the eyes too much and makes it hard to focus on the signature. '''I. angle brackets decorator syntax''' {{{ <classmethod> def foo(arg1,arg2): ... <accepts(int,int), returns(float)> def bar(low,high): ... }}} * + Same advantages of Pie decorator syntax * + Doesn't need a new character * - Angle brackets are "unpaired" characters * - Parsing of greater-than and less-than becomes more fragile. * - No implementation currently exists. '''J1. new keyword decorator syntax''' {{{ decorate classmethod: def foo(arg1,arg2): ... decorate accepts(int,int), returns(float): def bar(low,high): ... }}} * + Uses widely known python syntax * + Doesn't need extra characters with special meaning * + Allows many decorated functions to be declared with a single statement * - New keyword * - Increases minimum ident level on decorated functions (see following question) * - Inconsistent identation level between methods with/without decorators (see following question) * - way to much indenting if there are multiple decorators. (see following question) * - No implementation currently exists. Wouldn't be possible to allow both syntaxes?: {{{ decorate classmethod def foo(arg1, arg2): ... decorate classmethod: def foo(arg1, arg2): }}} '''J2. expand the def suite''' {{{ decorate: classmethod def foo(arg1,arg2): ... decorate: accepts(int,int) returns(float) def bar(low,high): ... }}} * + Uses widely known python syntax * + Doesn't need extra characters with special meaning * + Allows many decorated functions to be declared with a single statement * - New keyword * - Overkill for the simple case like classmethod. * - Many people felt it was wrong use of an identation suite. * - Technical problems with the current grammar parser if a suite *starts* with an optional part. (Ending with an optional part, such as "else:" is OK, but starting with one is not. * - No implementation currently exists. '''K. partitioned syntax syntax''' * Use pie-decorator syntax (or some other complex syntax) when arguments are to be passed * use inline syntax when no arguments are necessary {{{ def classmethod foo(arg1,arg2): ... }}} * + Simple for simple cases, poweful when needed * + Obviousely attached to the function * - The natural place where everyone looks for the function name now is a possible container for other information This is debatable. "Natural" will change if this is accepted. The natural place to find the fnction name will be after any simple decorators and before the argument list. * - Complicates things like colorization and other functions of helper tools Other syntaxes will need to be colorized too and will thus complicate colorization. * - Since both methods are legal, it has all the downsides of either syntax, in terms of what it does to the rest of the language or newbie confusion. * - No implementation currently exists. '''L. Keyword other than as and with before def''' {{{ using classmethod def foo(arg1,arg2): ... using accepts(int,int) using returns(float) def bar(low,high): ... }}} * + Most advantages of @decorators. * + Reads in english well * + No special character * - New keyword * - A lot of the drawback of @decorators * - No implementation currently exists. == Thinking ahead to Python 3 ? == [http://groups.google.com/groups?hl=en&lr=&ie=UTF-8&safe=off&selm=Pine.LNX.4.44.0408050856390.31290-100000%40ccc9.wpi.edu&rnum=1 Christopher King] makes the point that we are trying to do too much with decorators: declare class/static methods, describe function metadata, and mangle functions. It might be best to think about what is best for each separately. How might fully loaded functions look in the future? Christopher King's example: {{{ def classmethod foo(self,a,b,c): """Returns a+b*c.""" {accepts: (int,int,int), author: 'Chris King'} return a+b*c }}} Another possible example (keyword support for staticmethod & classmethod, visual basic-like typing using the "as" keyword for adapters, "with" code blocks): {{{ def classmethod foo(a as int, b as int, c as list) as list: """Returns a+b*c.""" listcopy = [] with synchronized(lock): listcopy[] = c[] return a+b*listcopy }}} Here it is with the @ symbol: {{{ @author('Chris King') @accepts(int,int,list) @classmethod def foo(self,a,b,c): """Returns a+b*c.""" return a+b*c }}} |
Support for decorators was proposed for Python in [http://www.python.org/peps/pep-0318.html PEP 318], and will be implemented in Python 2.4.
What is a decorator
A decorator is a software design pattern. Decorators dynamically alter the functionality of a function, method, or class without having to directly use subclasses or change the source code of the function being decorated.
For more information about the decorator pattern in general, see:
Debate about decorators in Python
The winning syntax as of now uses the '@' symbol, as described in [http://mail.python.org/pipermail/python-dev/2004-June/045516.html this message]. Mark Russell implemented this version. [http://mail.python.org/pipermail/patches/2004-July/015452.html Here] is the message describing the patch he checked in.
There has been a long discussion about the syntax to use for decorators in Python. See for example these threads:
Examples
Related Resources
See also: MixIns, MetaClasses
Current Decorator Proposals
After the @decorator syntax was "accepted", lots of people threw up alarms and a huge series of threads started exploding on Python-dev. Here are the current alternatives that I could find that are being argued, with pros and cons:
I give two examples that might be common uses in the future. Classmethod declarations, and something like static typing (adapters), declaring what type parameters a function expects and returns.
A. pie decorator syntax
@classmethod def foo(arg1,arg2): ... @accepts(int,int) @returns(float) def bar(low,high): ...
- + Implementation already exists (and is in Python 2.4a2)
- + Java-like, so not completely unknown to everyone.
- + Makes the syntax obvious visually (i.e., obviously not a normal statement)
- + Will not be silently ignored
- + Compile-time
- + One decorator per line
- + Separate from the def syntax (desired by some for making decoration stand out and keeping def the same)
- + Currently not used in Python so @decorators can be inserted anywhere, such as after parameters or inside expressions.
- + The @ special character will make syntax highlighting easier than it would be for normal statements in "magic" locations.
- - Separate from the def syntax (undesired by some for simple decorators like classmethod/staticmethod)
- - Ugly?
- - Introduces a new character in the language
- - The @ special character is used in IPython and Leo
- - Punctuation-based syntax raises Perlfears. But no "obvious" keyword or expression has been suggested.
- - Because of no indentation, looks confusing when definitions are not separated by empty lines. But adding empty lines makes it hard to determine where the function definition truly starts.
- - That's the first case in Python where a line following another one with the same identation has a meaning.
- - Breaks in interactive shell
- - Comes before the def keyword. Supercedes the function declaration itself, thus applying modifications implicitly to something not yet defined.
- - @staticmethod may look like staticmethod is not a defined variable, but a compile time option
- - Requires the programmer to scan an arbitrary distance from the def in *both* directions to see everything of interest (arg, decs, etc.).
- - Cannot be "folded" by editors as easily, as an arbitrary number of prefix lines should be folded with the body (otherwise, folding becomes less useful).
- 0 The @ character is often used (in other languages) to mean "attribute". For annotations, this is good. For more active decorators, it may not be so good.
FWIW, here is Guido's jumble example in this syntax.
class C(object): @staticmethod @funcattrs(grammar="'@' dotted_name [ '(' [arglist] ')' ]", status="experimental", author="BDFL") def longMethodNameForEffect(longArgumentOne=None, longArgumentTwo=42): """This method blah, blah. It supports the following arguments: - longArgumentOne -- a string giving ... - longArgumentTwo -- a number giving ... blah, blah. """ raise NotYetImplemented
And here is an example taken from the current test_decorators.py. This exposes the problem of using two lines together with some meaning but without identation.
class TestDecorators(unittest.TestCase): ... def test_dotted(self): decorators = MiscDecorators() @decorators.author('Cleese') def foo(): return 42 self.assertEqual(foo(), 42) self.assertEqual(foo.author, 'Cleese')
B. list-before-def syntax
[classmethod] def foo(arg1,arg2): ... [accepts(int,int), returns(float)] def bar(low,high): ...
- + Implementation already exists
- + C# like
- + Can be made backwards compatible-ish, with a "hack"
- + Doesn't cause breakage in existing code-searching tools
- + Use visually existing syntax
- - Doesn't cause breakage in existing code-searching tools
- - Would not work in interactive mode (list would be interpreted right away)
- EuroPython didn't like it ( why? ) (hard to teach newbies about the magic)
- - The backwards compatability wouldn't be portable to Jython
- - Looks like a normal expression, but has "magic" behavior of altering a function object
- - Breaks in interactive shell
- - Harder to highlight (looks like a normal list)
C1. list-after-def syntax
def foo(arg1,arg2) [classmethod]: ... def bar(low,high) [accepts(int,int), returns(float)]: ...
- + Implementation already exists
- + Also somewhat C#-like
- + Was a "community favorite" at one time
- + Clearly a part of function declaration
- + Looks ok for simple decorators such as classmethod
- + Won't break simplistic code analyzers or grep for function def
- + Use visually existing syntax
- + Allows one-liner definitions
- - Long lists of decorators/arguments cause ugly line wraps
- - Little to distinguish it visually from argument list
- or 0 For long argument list, decorators are very far from def. I see being too close to def as a minus.
- - Guido hates it because it hides crucial information after the signature, it's easy to miss the transition between a long argument list and a long decorator list, and it's cumbersom to cut and paste a decorator list for reuse.
- - Creates another meaning for []. Since it does so inside the function definition, it will be a distraction for beginners.
- 0 Brackets are used in other fields to indicate an annotation of some sort (but in Python, it doesn't)
I don't see why longs lists of decorators are an issue with this syntax. Consider the following example:
def foo(arg1, arg2) [ complicated(manyArgs=1, notTooUgly='yes'), even_more_complicated(42)]: ...
That doesn't look particularly ugly to me.
---
It also isn't very long.
Here is an [http://mail.python.org/pipermail/python-dev/2004-August/047112.html example Guido just sent to python-dev]:
class C(object): def longMethodNameForEffect(longArgumentOne=None, longArgumentTwo=42) [ staticmethod, funcattrs(grammar="'@' dotted_name [ '(' [arglist] ')' ]", status="experimental", author="BDFL") ]: """This method blah, blah. It supports the following arguments: - longArgumentOne -- a string giving ... - longArgumentTwo -- a number giving ... blah, blah. """ raise NotYetImplemented
And he editorializes:
That's a total jumble of stuff ending with a smiley. (True story: I left out the colon when typing up this example and only noticed in proofing.) Problems with this form: - it hides crucial information (e.g. that it is a static method) after the signature, where it is easily missed - it's easy to miss the transition between a long argument list and a long decorator list - it's cumbersome to cut and paste a decorator list for reuse, because it starts and ends in the middle of a line Given that the whole point of adding decorator syntax is to move the decorator from the end ("foo = staticmethod(foo)" after a 100-line body) to the front, where it is more in-your-face, it should IMO be moved all the way to the front.
C2. list-after-def syntax with a (pseudo-)keyword
def foo(arg1,arg2) using [classmethod]: ... def bar(low,high) using [accepts(int,int), returns(float)]: ...
This combines C1 with a keyword; it general, it has all the advantages of either, so I will only list those that are unique to the combination.
- + Groups the decorators with a list, but explains what they are doing, so the list no longer has a magical new meaning.
- + Easily extended; No special characters are "used up", and in the future, other pseudo-keywords could be added.
- + The pseudo-keyword makes it easier to see the separation between the argument tuple and the decorator list.
- + The pseudo-keyword can act as an implicit line-continuation, which helps with (but does not solve) the midline problem.
- - Some proponents objected to adding a keyword, because of more typing.
- 0 Makes the decoration look like a second-class or optional part of the definition. This is true, but may have caused some emotional objection.
- - There was very little agreement on which word should be used.
- No implementation currently exists (it may be a simple variation of the implementation of C1).
FWIW, here is Guido's jumble example in this syntax.
class C(object): def longMethodNameForEffect(longArgumentOne=None, longArgumentTwo=42) using [staticmethod, funcattrs(grammar="'@' dotted_name [ '(' [arglist] ')' ]", status="experimental", author="BDFL")]: """This method blah, blah. It supports the following arguments: - longArgumentOne -- a string giving ... - longArgumentTwo -- a number giving ... blah, blah. """ raise NotYetImplemented
Without the pseudo-keyword acting as line continuation it reads :
class C(object): def longMethodNameForEffect(longArgumentOne=None, longArgumentTwo=42) using [ staticmethod, funcattrs(grammar="'@' dotted_name [ '(' [arglist] ')' ]", status="experimental", author="BDFL")]: """This method blah, blah. It supports the following arguments: - longArgumentOne -- a string giving ... - longArgumentTwo -- a number giving ... blah, blah. """ raise NotYetImplemented
which feel is more consistent with the rest of python parsing-wise, without decreasing readability...
C3. tuple-after-def syntax with a (pseudo-)keyword
def foo(arg1,arg2) using classmethod,: ... def bar(low,high) using accepts(int,int), returns(float): ... class C(object): def longMethodNameForEffect(longArgumentOne=None, longArgumentTwo=42) using ( staticmethod, funcattrs(grammar="'@' dotted_name [ '(' [arglist] ')' ]", status="experimental", author="BDFL")): """This method blah, blah. It supports the following arguments: - longArgumentOne -- a string giving ... - longArgumentTwo -- a number giving ... blah, blah. """ raise NotYetImplemented
Very similar to C2, but with those slight differences
- + Nicer for one-line decoration when using multiple decorators.
- - The hanging comma feel strange for 1-element tuple, and will probably often been forgotten.
- - Decorator and arguments looks (too?) similar for multiline case.
- - No implementation currently exists.
The 1st drawback could be removed if one allows both tuple and single-element after the pseudo-keyword, trading consistency for readability and convenience.
C4. tuple-after-def syntax with a % operator
def foo(arg1,arg2) % classmethod: ... def bar(low,high) % accepts(int,int), returns(float): ... class C(object): def longMethodNameForEffect(longArgumentOne=None, longArgumentTwo=42) # make implicit linebreak possible here % (staticmethod, funcattrs(grammar="'@' dotted_name [ '(' [arglist] ')' ]", status="experimental", author="BDFL")): """This method blah, blah. It supports the following arguments: - longArgumentOne -- a string giving ... - longArgumentTwo -- a number giving ... blah, blah. """ raise NotYetImplemented # this is also possible for consistency: foo %= classmethod bar %= (accepts(int,int), returns(float))
Very similar to C3, but with those slight differences
- + Nicer for one-line decoration when using multiple decorators.
- + Similar to use of % in string formatting operation
- - Decorator and arguments looks (too?) similar for multiline case.
- - No implementation currently exists.
One more point: % could also be used in chained fashion:
bar = bar % accepts(int,int) % returns(float)
(making it similar to E2 below)
D. list at top of function body syntax
def foo(arg1,arg2): [classmethod] ... def bar(low,high): [accepts(int,int), returns(float)] ...
- + Also somewhat C#-like
- + Consistent with how docstrings are used.
- + Looks ok for simple or complex decorators
- + Won't break simplistic code analyzers or grep for function def
- + Solves line wrap problem with above proposal
0 There is a hack that implements this now [http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/286147 here].
- - Guido's europython presentation said this didn't win out, but not why
- - Adds 'magic' behavior to a normal python expression (lists). Not exactly true: there is nothing magic in string when it's used in docstring - it's a normal string in the "magic" place.
- - Compatibility issue: program that is working under 2.4 will not work properly under earlier versions without any explanation (old syntax compatible decorators will not blow in your face)
- 0 Perhaps decorators should be allowed before or after the docstring. If you have to choose, I'd choose making it before the docstring.
- - No implementation currently exists.
- Guido ruled out any solution involving special syntax inside the block, because "you shouldn't have to peek inside the block to find out important external properties of the function." [http://mail.python.org/pipermail/python-dev/2004-August/047279.html]
E1. pie decorator at top of function body syntax
def foo(arg1,arg2): @classmethod ... def bar(low,high): @accepts(int,int) @returns(float) ...
- Same as above but with pie syntax.
- Could use @doc too as a docstring alternative
- - No implementation currently exists.
- Guido ruled out any solution involving special syntax inside the block, because "you shouldn't have to peek inside the block to find out important external properties of the function." [http://mail.python.org/pipermail/python-dev/2004-August/047279.html]
E2. vbar decorator at top of function body syntax
def foo(arg1,arg2): |classmethod ... def bar(low,high): |accepts(int,int) |returns(float) ... def longMethodNameForEffect(longArgumentOne=None, longArgumentTwo=42): |staticmethod |funcattrs(grammar="'@' dotted_name [ '(' [arglist] ')' ]", status="experimental", author="BDFL") """This method blah, blah. It supports the following arguments: - longArgumentOne -- a string giving ... - longArgumentTwo -- a number giving ... blah, blah. """
- + Consistent with how docstrings are used.
- + Looks ok for simple or complex decorators
- + Won't break simplistic code analyzers or grep for function def
- + Vertical bars visually "attach" the decorators to the name.
- + Decorators are indented, so it's clear that they modify the function.
- - Indented decorators look like they should be evaluated when the function is called, not when the function is parsed. I have the same objection to docstrings, though.
- + Reads naturally as "pipe" operator to unix hackers (which is semantically correct, since the defined function gets passed through the decorators, one at a time, and the result is used.
- - Misleading to Unix hackers, since the order of evaluation is "backwards".
- + Doesn't use up one of the currently unused characters (such as "@") -- it's always possible that we'll find another good use for those later.
- - For some fonts, "|" looks similar to "I" or "l" or "1". Some think that code highlighting would remove this problem, but with normal-sized fonts, there are not many pixels in it to show a color clearly. "@" has a big blob of pixels, and is very distinct.
- - The key with "|" on it is often in an awkward location on laptop keyboards
- 0 Perhaps decorators should be allowed before or after the docstring. If you have to choose, I'd choose making it before the docstring.
- - No implementation currently exists.
- - When using just one bar, it isn't noticable enough. It seems more a part of the actual decorator function name than something demarking the function.
- - When using multiple decorators, the pattern formed by the vertical bars draws the eyes too much and makes it hard to focus on the signature.
- Guido ruled out any solution involving special syntax inside the block, because "you shouldn't have to peek inside the block to find out important external properties of the function." [http://mail.python.org/pipermail/python-dev/2004-August/047279.html]
E3. vbar decorator after arg
def longMethodNameForEffect(longArgumentOne=None, longArgumentTwo=42) |staticmethod |funcattrs(grammar="'@' dotted_name [ '(' [arglist] ')' ]", status="experimental", author="BDFL"): """This method blah, blah. It supports the following arguments: - longArgumentOne -- a string giving ... - longArgumentTwo -- a number giving ... blah, blah. """ def bar(low,high) |accepts(int,int) |returns(float): ... def foo(arg1,arg2) | classmethod: ...
An alternative (inspired by a typing error I corrected in E2 in the Guido example) would be to put vbar decorator before the colon... Basically it has the same characteristic than E2, with the following slight differences:
- + More obviously attached to function definition
- + Possible 1-line version as readable as the inline syntax (see F.) for short function/decoration
- - More difficult to parse?
- - New syntax having no equivalent in other part of python (but all @ / | propositions suffer from that )
- - No implementation currently exists.
F. inline syntax
def classmethod foo(arg1,arg2): ... ?
- + Simple
- + More readable/natural
- + Obviousely attached to the function
- - Does not allow for arguments to the decorator inline, or multiple decorators
- - The natural place where everyone looks for the function name now is a possible container for other information
- - Complicates things like colorization and other functions of helper tools
- - Many people don't like the idea of having something between 'def' and the function name
- - Breaks etags (couldn't it be fixed?)
- - No implementation currently exists.
G. as decorator
as classmethod def foo(arg1,arg2): ... ?
- + Non-punctuation based
- + Does not use an existing mechanism with 'magic' behavior
- - Guido specifically vetos: "as" means "rename" in too many logical, common places that are
- - No implementation currently exists.
H. pie decorator using a different character
For example, using the '|' character:
|classmethod def foo(arg1,arg2): ... |accepts(int,int) |returns(float) def bar(low,high): ...
Same pros and cons as @decorator, but additionally:
- + Likely to be a trivial change to what is already in 2.4a2.
- + It doesn't break Leo, IPython, or any other tool that uses @ as a special character.
- + The association with pipes makes some sense: "take this thing and pass it through that thing to get a modified thing".
- - Most characters (including pipe) already have a meaning. Ending an expression at the linebreak will probably keep these from becoming ambiguous, but ... that gets fragile.
Edit conflict! Other version:
- - Misleading to Unix hackers, since the order of evaluation is "backwards".
- - For some fonts, "|" looks similar to "I" or "l" or "1" Some think that code highlighting would remove this problem, but with normal-sized fonts, there are not many pixels in it to show a color clearly. "@" has a big blob of pixels, and is very distinct.
Edit conflict! Your version:
End of edit conflict
Edit conflict! Other version:
Edit conflict! Your version:
- + The association with pipes makes some sense: "take this thing and pass it through that thing to get a modified thing".
- - Most characters (including pipe) already have a meaning. Ending an expression at the linebreak will probably keep these from becoming ambiguous, but ... that gets fragile.
- - For some fonts, "|" looks similar to "I" or "l"; but code highlighting would remove this problem.
End of edit conflict
- - The key with "|" on it is often in an awkward location on laptop keyboards
- - When using just one bar, it isn't noticable enough. It seems more a part of the actual decorator function name than something demarking the function.
- - When using multiple decorators, the pattern formed by the vertical bars draws the eyes too much and makes it hard to focus on the signature.
I. angle brackets decorator syntax
<classmethod> def foo(arg1,arg2): ... <accepts(int,int), returns(float)> def bar(low,high): ...
- + Same advantages of Pie decorator syntax
- + Doesn't need a new character
- - Angle brackets are "unpaired" characters
- - Parsing of greater-than and less-than becomes more fragile.
- - No implementation currently exists.
J1. new keyword decorator syntax
decorate classmethod: def foo(arg1,arg2): ... decorate accepts(int,int), returns(float): def bar(low,high): ...
- + Uses widely known python syntax
- + Doesn't need extra characters with special meaning
- + Allows many decorated functions to be declared with a single statement
- - New keyword
- - Increases minimum ident level on decorated functions (see following question)
- - Inconsistent identation level between methods with/without decorators (see following question)
- - way to much indenting if there are multiple decorators. (see following question)
- - No implementation currently exists.
Wouldn't be possible to allow both syntaxes?:
decorate classmethod def foo(arg1, arg2): ... decorate classmethod: def foo(arg1, arg2):
J2. expand the def suite
decorate: classmethod def foo(arg1,arg2): ... decorate: accepts(int,int) returns(float) def bar(low,high): ...
- + Uses widely known python syntax
- + Doesn't need extra characters with special meaning
- + Allows many decorated functions to be declared with a single statement
- - New keyword
- - Overkill for the simple case like classmethod.
- - Many people felt it was wrong use of an identation suite.
- - Technical problems with the current grammar parser if a suite *starts* with an optional part. (Ending with an optional part, such as "else:" is OK, but starting with one is not.
- - No implementation currently exists.
K. partitioned syntax syntax
- Use pie-decorator syntax (or some other complex syntax) when arguments
- are to be passed
- use inline syntax when no arguments are necessary
def classmethod foo(arg1,arg2): ...
- + Simple for simple cases, poweful when needed
- + Obviousely attached to the function
- - The natural place where everyone looks for the function name now is a possible container for other information
- This is debatable. "Natural" will change if this is accepted. The natural place to find the fnction name will be after any simple decorators and before the argument list.
- - Complicates things like colorization and other functions of helper tools
- Other syntaxes will need to be colorized too and will thus complicate colorization.
- - Since both methods are legal, it has all the downsides of either syntax, in terms of what it does to the rest of the language or newbie confusion.
- - No implementation currently exists.
L. Keyword other than as and with before def
using classmethod def foo(arg1,arg2): ... using accepts(int,int) using returns(float) def bar(low,high): ...
- + Most advantages of @decorators.
- + Reads in english well
- + No special character
- - New keyword
- - A lot of the drawback of @decorators
- - No implementation currently exists.
Thinking ahead to Python 3 ?
[http://groups.google.com/groups?hl=en&lr=&ie=UTF-8&safe=off&selm=Pine.LNX.4.44.0408050856390.31290-100000%40ccc9.wpi.edu&rnum=1 Christopher King] makes the point that we are trying to do too much with decorators: declare class/static methods, describe function metadata, and mangle functions. It might be best to think about what is best for each separately.
How might fully loaded functions look in the future?
Christopher King's example:
def classmethod foo(self,a,b,c): """Returns a+b*c.""" {accepts: (int,int,int), author: 'Chris King'} return a+b*c
Another possible example (keyword support for staticmethod & classmethod, visual basic-like typing using the "as" keyword for adapters, "with" code blocks):
def classmethod foo(a as int, b as int, c as list) as list: """Returns a+b*c.""" listcopy = [] with synchronized(lock): listcopy[] = c[] return a+b*listcopy
Here it is with the @ symbol:
@author('Chris King') @accepts(int,int,list) @classmethod def foo(self,a,b,c): """Returns a+b*c.""" return a+b*c