Using a decorator to *remove* parameters from a call

Jon Clements joncle at googlemail.com
Mon Apr 13 06:52:14 EDT 2009


On 13 Apr, 11:11, Michel Albert <exh... at gmail.com> wrote:
> A small foreword: This might look like a cherrypy-oriented post, and
> should therefore go to the cherrypy group, but if you read to the end,
> you'll see it's a more basic python problem, with cherrypy only as an
> example. ;)
>
> From the decorator PEP (318) I get it that you can /add/ parameters to
> a call. Say you do something like this:
>
> @mydeco( foo="bar" )
> def myfunc( hello ):
>    print foo, foo
>
> However, this is not what I would like to do. I would like to take
> away one or more attributes using a decorator. My goal is to
> centralize the logic associated with that parameter. In my particular
> example, I am writing a cherrypy application (more specifically,
> turbogears1) and I would like all controller method to accept a "lang"
> and a "skin" attribute. As a simple, but hands-on example, imagine
> code like this (http://python.pastebin.com/f25f2429b):
>
> class MyController(Controller):
>
>     @expose()
>     def index(self, skin="default", lang="en"):
>         set_skin( skin )
>         set_language( lang )
>         return "Hello skinned world"
>
>     @expose()
>     def foo(self, skin="default", lang="en"):
>         set_skin( skin )
>         set_language( lang )
>         return "bar"
>
> This becomes cumbersome however for a large application with many
> controllers and methods. Always adding the parameters to the methods
> and function calls into the method bodies looks way to repetitive for
> my taste.
>
> Currently I solve this by using a cherrypy filter which removes those
> parameters from the HTTP-Request and puts them into the session
> object. This looked wrong to me though right from the start. Also, the
> cherrypy docs advise against the use of "filters" for application
> logic. But this looks pretty much like that to me.... somewhat. Worse
> yet, filters are not supported in Turbogears2 any longer. Which makes
> porting my app not straight-forward, as I have to port my filters and
> write WSGI middleware instead.
>
> I would prefer a syntax like this (http://python.pastebin.com/
> f462bc29c)
>
> class MyController(Controller):
>
>     @expose()
>     @skinnable
>     @translatable
>     def index(self):
>         return "Hello skinned world"
>
>     @expose()
>     @skinnable
>     @translatable
>     def foo(self):
>         return "bar"
>
> This, to me has several advantages: The logic is "encapsulated" in the
> application code itself. I do not need to rely on framework specific
> features (be it cherrypy or wsgi). This would make the application
> more self-contained and hence more future proof.
>
> But right here lies my problem. If you are not familiar with CherryPy,
> let me digress for a tiny bit to give you the necessary backgroud:
>
> Inside a controller, an "exposed" method is reachable via HTTP
> requests. It's possible to force request parameters. If that is the
> case (as in my case it is), then the call will only be successful, if
> all parameters received a vaule and no unknown parameters have been
> passed. Assume the following signature:
>
> def index(self)
>
> This will successfully return an HTTP Response when the client
> requested the resource on the URL "/index". If the client adds a query
> string, say "/index?lang=en" the call will *fail* as this parameter is
> unkown. For this to work the signature must read:
>
> def index(self, lang)
>
> or
>
> def index(self, lang="en")
>
> Right.... end of digression, back to topic:

Haven't finished coffee yet, plus it's a bank holiday! [/excuse]

Can't that be changed to def index(self, **kwdargs) -- then your
decorators can operate something like set_language(kwdargs.get('lang',
'en')) and then delete the key....

Anyway, back to more coffee!

>
> My ultimate question is: Is it possible to write a decorator that
> removes a parameter from a call and return a function without that
> parameter? Something along the lines (http://python.pastebin.com/
> f257877cd):
>
> def translatable(f):
>     "Note that this code is only an example and will not work!"
>     lang = f.__get_parameter_value__("lang")
>     f.__remove_parameter__("lang")
>     do_something_with_lang(lang)
>     return f
>
> I am aware, that this depends on *when* the decorator call is
> executed. If its called whenever the decorated function is called,
> then there should be some way to handle this. If it's called at
> "compile-time", then I suppose it's not going to be possible.
>
> A usage scenario would be this:
>
> # --- definition ---------------
> @translatable
> def index(self):
>    return _("Hello", lang)
>
> # --- call ----------------------
> obj.index( lang="de")
> obj.index()
>
> As you can see, the method definition does not contain the "lang"
> parameter in the signature, but I want to be able to (optionally) set
> it during a call.
>
> I hope I made my ideas clear enough. I would be very positively
> surprised if this worked. It would make my application code much
> easier to read, understand and follow. The filters currently do a lot
> of magic "behind-the-scenes" and if somebody else needs to jump in and
> code on that application they will surely ask themselves "where the
> ****** is that ***** lang variable set?" :) And I'd like to make it as
> easy as possible for everyone :)
>
> Or, maybe you even have a better solution in mind? I'm open for
> suggestions.




More information about the Python-list mailing list