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.
In Python, decorators can be either functions or classes. In both cases, decorating adds functionality to existing functions. When we decorate a function with a class, that function becomes an instance of the class.
To decorate a method in a class, first use the '@' symbol followed by the name of the decorator function. A decorator is simply a function that takes a function as an argument and returns yet another function. Here, when we decorate, multiply_together with integer_check, the integer function gets called.
To make instance of any class callable, we can define the __call__ special method in that class. Using this technique we can attach behavior to class instances by defining the __call__ method which can even take arguments, and making the instance callable.
How to Make an Object Callable. Simply, you make an object callable by overriding the special method __call__() . __call__(self, arg1, .., argn, *args, **kwargs) : This method is like any other normal method in Python. It also can accept positional and arbitrary arguments.
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()
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With