6
def decorator(fn):
    def wrapper(*args, **kwargs):
        print 'With sour cream and chives!',
        return fn(*args, **kwargs)
    return wrapper

class Potato(object):
    def __call__(self):
        print 'Potato @ {} called'.format(id(self))

spud = Potato()
fancy_spud = decorator(Potato())

With this code we have two instances of callable class, one is decorated and one is plain:

>>> spud()
Potato @ 140408136280592 called
>>> fancy_spud()
With sour cream and chives! Potato @ 140408134310864 called

I wonder if it is somehow supported to use the @decorator syntax on a callable for just one instance - as opposed to decorating the class / method, which would apply to every instance. According to this popular answer, the @syntax is just sugar for:

function = decorator(function)

But is it an over-simplification? With all my half-baked attempts it seems only to work when the syntax occurs before def, class, whitespace or @another_decorator.

@decorator
baked = Potato()

That's a SyntaxError.

baked = Potato()
@decorator
baked

Also SyntaxError.

@decorator
def baked(_spud=Potato()):
    return _spud()

Works, but is ugly and kinda cheating.

Community
  • 1
  • 1
wim
  • 338,267
  • 99
  • 616
  • 750

2 Answers2

5

Yes, it's an oversimplification. If we look at the grammar, decorator only appears in the rule decorators, which only appears as part of a classdef or funcdef:

decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE
decorators: decorator+
decorated: decorators (classdef | funcdef)

What the language reference says (and I think this is what's being repeated in the linked answer) is that

@f1(arg)
@f2
def func(): pass

is equivalent to

def func(): pass
func = f1(arg)(f2(func))

and similarly for class definitions. But that doesn't mean that the @decorator syntax can be applied just anywhere; it's only valid immediately before a function or class definition.

As an aside, even the official docs aren't strictly correct; at the time the decorator is called the function (or class) isn't bound into the enclosing namespace or scope, so the given syntaxes are not entirely equivalent.

There's something interesting about the def and class statements, which I think is part of the reason that they're the only statements supported by @decorator syntax: they're the only way in Python to bind a name to an object that knows what that name is.

Finally, here's another way to invoke a decorator that you might like:

@decorator
class baked:
    __metaclass__ = lambda *_: Potato()
ecatmur
  • 152,476
  • 27
  • 293
  • 366
3

You question states:

According to this popular answer, the @syntax is just sugar for:

function = decorator(function)

However, it's more accurate to say that

@decorator
def function():
    pass

Is syntactic sugar for:

def function():
    pass
function = decorator(function)

Decorators are designed to decorate function, method or class definitions, specifically. The PEP that introduced class decorators describes the grammar:

decorated: decorators (classdef | funcdef)

funcdef: 'def' NAME parameters ['->' test] ':' suite

As you can see, a decorator must come immediately before a classdef or funcdef, so there is no way to use it directly on an instance of a callable class.

dano
  • 91,354
  • 19
  • 222
  • 219