Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I decorate an instance of a callable class?

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.

like image 309
wim Avatar asked Jun 09 '14 14:06

wim


People also ask

Can you decorate classes Python?

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.

How do you decorate a class method?

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.

How do I make an instance callable?

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 do you make a class object 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.


2 Answers

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()
like image 163
ecatmur Avatar answered Oct 11 '22 01:10

ecatmur


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.

like image 32
dano Avatar answered Oct 11 '22 02:10

dano