Contents
- About this page
- What is a Decorator
- What is a Python Decorator
- Debate about decorators in Python
- Examples
-
Current Python Decorator Proposals
- A1. pie decorator syntax
- A2. pie decorator and space syntax
- B. list-before-def syntax
- C1. list-after-def syntax
- C2. list-after-def syntax with a (pseudo-)keyword
- C3. tuple-after-def syntax with a (pseudo-)keyword
- C4. tuple-after-def syntax with a % operator
- D1. list at top of function body syntax
- D2. 'dot'decorators at top of function body syntax
- E1. pie decorator at top of function body syntax
- E2. vbar decorator at top of function body syntax
- E3. vbar decorator after arg
- E4. keyword decorator at top of function body syntax
- F. inline syntax
- F2. inline syntax + new keyword (decodef for example)
- G. as decorator
- H. pie decorator using a different character
- I. angle brackets decorator syntax
- J1. new keyword decorator syntax
- J2. expand the def suite
- J3. two part def suite
- J4 two part def suite with "@" decorators
- J5 two part def suite with "@" decorators and colon
- K. partitioned syntax syntax
- L. Keyword other than as and with before def
- M. Making def an expression / letting it return a value
- N: Another Proposal (supplied by Anonymous)
- Decorator Syntax Breakdown
- Thinking ahead to Python 3 ?
About this page
This page largely documents the history of the process of adding decorators to Python.
If you're just interested in what decorators or the '@' symbol mean in Python, see the Wikipedia page http://en.wikipedia.org/wiki/Python_syntax_and_semantics#Decorators or PEP 318.
What is a Decorator
A decorator is the name used for 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:
What is a Python Decorator
The "decorators" we talk about with concern to Python are not exactly the same thing as the DecoratorPattern described above. A Python decorator is a specific change to the Python syntax that allows us to more conveniently alter functions and methods (and possibly classes in a future version). This supports more readable applications of the DecoratorPattern but also other uses as well.
Support for the decorator syntax was proposed for Python in PEP 318, and will be implemented in Python 2.4.
Note that the current proposal actually only decorates functions (including methods). Extending it to classes or even arbitrary code is possible, but Guido wasn't sure it made sense. (Later versions might become more permissive, but they can't easily snatch functionality back.)
Debate about decorators in Python
The winning syntax as of now uses the '@' symbol, as described in this message. Mark Russell implemented this version. 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.
Examples
See PythonDecoratorLibrary for more complex and real-world examples. See also MixIns and MetaClasses for related resources.
Current Python Decorator Proposals
See section 6 for a categorization of different proposals; this section just provides concrete examples.
Decorator Poll (consider the poll now closed)
Here is an online poll where you can vote between a few different alternative syntaxes for python decorators: http://wiki.wxpython.org/index.cgi/PythonDecoratorsPoll
A more complete poll is currently running on comp.lang.python, with the unfortunate name of "Alternate decorator syntax decision", using the options on this WikiPage as the candidates. Please visit that thread and express your preference!
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.
A1. 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 (makes it clearer to read, write and change order of decorators)
- + 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.
- + All decorators line up in one column immediately above the function name (easy to browse and see what's going on).
- + 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.
- - @ is a relatively arbitrary character choice; no intuitive reason why it would indicate a decorator
- 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.
- - Some people would like the @ character to be available as an overloadable operator - for example, as a binary operator for matrix multiplication (element-by-element and matrix multiplication may be usefully applied to a single object, so there's arguably a need for a new operator rather than simply overriding binary * on a Matrix class).
- - 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).
- - While the Language Spec does not promise that this character won't be used, neither does it reserve *any* character for user extensions. '@' is one of only three that are available, and another '$' is also likely to get used up by 2.4
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 NotYetImplementedAnd 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 or vertical whitespace.
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')
A2. pie decorator and space syntax
@ classmethod
def foo(arg1,arg2):
...
@ accepts(int,int)
@ returns(float)
def bar(low,high):
...* 0 This is more readable for some people, less readable for others.
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 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 NotYetImplementedAnd 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 NotYetImplementedWithout 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 NotYetImplementedwhich feel is more consistent with the rest of python parsing-wise, without decreasing readability...
(See also J4 below, which moves the keyword, and uses "@" signs to make the decorators stand out more.)
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 NotYetImplementedVery 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)
D1. 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 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
- Guido rejects, because the function body should reflect what the function does; decorators are "outside" the function, for its callers.
D2. 'dot'decorators at top of function body syntax
def bar(low,high):
.accepts(int,int)
.returns(float)
"""docstring"""
pass
def longMethodNameForEffect(longArgumentOne=None,
longArgumentTwo=42):
.staticmethod
.funcattrs(grammar="'@' dotted_name [ '(' [arglist] ')' ]",
status="experimental", author="BDFL")
"""
asdfasdf
"""
raise NotYetImplementedAdvantages/disadvantages of .decorators:
- + Unambiguous "target" for decorators, matches Python's precedents for indentation and modify-what-came-before
- + Decorators won't get lost in long argument lists (they are indented differently in Guido's preferred formatting, and are separated by the smiley in the all-args-on-individual-lines formatting)
- 0 Compatible with some future "with ...:" syntax, as decorators must immediately follow a 'def ...:' (or possibly a 'class ...:'), so if there is a 'with ...:' what follows cannot be a decorator.
- + Separate from def syntax (to keep def the same avoiding breakage)
- + No extra cut-and-paste issues, no extra indentation level.
+ No smileys. (Does that get a
or a ]: ?) - + Will not be silently ignored (doesn't use currently legal syntax)
- + Simple for simple decorators, supports complex decorators as cleanly as possible
- + Less ugly (YMMV). Doesn't feel like executable line noise (to 1 out of 1 pythonistas polled...)
- + No new punctuation or keywords (avoids breaking Leo/IPython/existing code)
- + Really nice syntax for assigning attributes, if desired (Not sure I like this overloading, but it /looks/ good)
def func():
.author = "Kevin Butler"
pass+/0 Syntax obvious visually (Someone will complain that the leading period gets lost - that person should switch to a fixed-width font, as used when coding. <.5 wink>) Easy to highlight.
- 0 Although it is a punctuation-based syntax, it is compatible with existing/proposed '.' usage ('.' can mean subordination to a containing construct like 'with', and passing the implicit "func" argument is analogous to passing "self")
- 0 Perhaps decorators should be allowed before or after the docstring. If you have to choose, I'd choose making it before the docstring.
- - Minor extension to use of '.'
- - Some people may have too much dust on their monitors
+ Could use .doc() or .doc = or .__doc__ = as a docstring alternative
- 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."
