Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing keyword arguments to a class method decorator

I have a class that has an output() method which returns a matplotlib Figure instance. I have a decorator I wrote that takes that fig instance and turns it into a Django response object.

My decorator looks like this:

class plot_svg(object):
    def __init__(self, view):
        self.view = view

    def __call__(self, *args, **kwargs):
        print args, kwargs
        fig = self.view(*args, **kwargs)
        canvas=FigureCanvas(fig)
        response=HttpResponse(content_type='image/svg+xml')
        canvas.print_svg(response)
        return response

and this is how it was being used:

def as_avg(self):
    return plot_svg(self.output)()

The only reason I has it that way instead of using the "@" syntax is because when I do it with the "@":

@plot_svg
def as_svg(self):
    return self.output()

I get this error:

as_svg() takes exactly 1 argument (0 given)

I'm trying to 'fix' this by putting it in the "@" syntax but I can't figure out how to get it working. I'm thinking it has something to do with self not getting passed where it's supposed to...

like image 540
priestc Avatar asked Mar 12 '10 05:03

priestc


1 Answers

Right: when you decorate with a class, instead of with a function, you have to make it a descriptor (give it a __get__ method, at least) to get the "automatic self". Simplest is to decorate with a function instead:

def plot_svg(view):

    def wrapper(*args, **kwargs):
        print args, kwargs
        fig = view(*args, **kwargs)
        canvas = FigureCanvas(fig)
        response = HttpResponse(content_type='image/svg+xml')
        canvas.print_svg(response)
        return response

    return wrapper

Background: the reason functions "become methods" (when defined in a class and accessed on an instance thereof by attribute-get notation), in other words, the reason such functions can get their automatic self is that they're descriptors -- the function type has a __get__.

A class doesn't have a __get__ method -- unless you explicitly add one. So why not just decorate with a function instead, as in the above example? This way you get the nice __get__ of functions automatically -- and as you see the nested function "lexical closure" property doesn't pose any problem at all (indeed, it simplifies things -- the nested function calls view, not self.view which might be rather confusing if self could mean either an instance of your decorator class OR an instance of the class whose method you're decorating...!-).

like image 185
Alex Martelli Avatar answered Oct 23 '22 05:10

Alex Martelli