views:

147

answers:

3

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...

+4  A: 

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...!-).

Alex Martelli
I'm confused by your answer. I had thought the substitution was equivalent to the expected lexical substitution. So: @plot_svg(view=some_view) def some_func(some_args): ... -> (minus intermediate variables/bindings) def some_func(some_args): ... a_plot_instance = plot_svg.__init__(view=some_view) some_func = a_plot_instance.__call__(some_args)Can you clarify? And I hate lack of comment formatting.
Charles Merriam
@charles.merriam: there **is** comment formatting. Use \*\* for **bold**, \* for *italics*, backticks (\`) for `code`, etc... You may need to use a backslash now and then...
ChristopheD
I'm confused by your answer. I had thought the substitution was equivalent to the expected lexical substitution. So: `@plot_svg(view=some_view) `\n`def some_func(some_args): ... `\n-> (minus intermediate variables/bindings) \n`def some_func(some_args): ... `\n`a_plot_instance = plot_svg.__init__(view=some_view) `\n`some_func = a_plot_instance.__call__(some_args)`\n Can you clarify?
Charles Merriam
@charles.merriam: If you don't start your comment with @Alex Martelli he will not be notified automatically and it's possible that he does not revisit the question then.
ChristopheD
@ChristopheD, I do get notified anyway on comments to my answers -- you may have "replied to my comments" in mind.
Alex Martelli
@charles.merriam, **your class is not a descriptor unless it defines `__get__`**, so "substitution was equivalent" makes no sense; a **function** is always a descriptor, a class instance not always, and I can't clarify any further! Study descriptors at http://users.rcn.com/python/download/Descriptor.htm -- or just use the solution I suggest, which is a function, thus a descriptor, and therefore works by getting the "automatic `self`" arg.
Alex Martelli
+1  A: 

OK, there seem to be a couple of issues.

First, some syntax. You are getting the "takes exactly one argument" error because your decorator, @plot_svg, is expecting one parameter (a view). There are other syntax errors as well.

Second, and more importantly, you want to write a function instead of a decorator. A decorator destroys your access to one concept and only gives you access to the combined concept. You just want a new function like this:

def as_svg(an_object):
    return django_response(an_object.output())

Alternately, I misunderstand your question from having a little too little code example.

Charles Merriam
A: 

Hi, Just try the following link

http://arunnambyar.blogspot.com/2010/05/python-decorators-in-depth.html

It is greaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaately Helpfull

Arun Mangatt