views:

104

answers:

2

While I'm aware that you can't reference self directly in a decorator, I was wondering if it's bad practice to work around that by pulling it from args[0]. My hunch is that it is, but I want to be sure.

To be more specific, I'm working on an API to a web service. About half the commands require a token to be passed that can be later used to undo it. What I would like is to make that token an optional parameter and if none is supplied, to generate one. Generating a token requires making an authenticated call to the server, which needs data from the object.

While I know I could do it:

def some_command(self, ..., undo_token = None):
    if undo_token = None:
        undo_token = self.get_undo_token()
    ...
    return fnord

I feel like there could be a better way than to have the same code in a dozen or so methods. My thought was to write a decorator:

@decorator
def undoable(fn, *args, **kwargs):
    if 'undo_token' not in kwargs:
        kwargs['undo_token'] = args[0].get_undo_token()
    return (fn(*args, **kwargs), kwargs['undo_token'])

So I can more cleanly write

@undoable
def some_command(self, ...):
    ...
    return foo

@undoable
def some_other_command(self, ...):
    ...
    return bar

Am I setting myself up for trouble down the line?

+2  A: 

Decorators extend the functionality of the function it decorates in a generic way. If decorators to do not make any assumption about the function or it's args or kwargs, it is in most generic form and can be easily used with many functions.

How ever, if you want to do something with what is being passed onto the function, it should be fine but their applicability is limited and can break, if the underlying details, which you have used in your decorator changes.

In the above decorator, if the object removes the method get_undo_token(), you will need to revisit the decorator too. It is fine to do that but document the constraints and also add that documentation to the method doc it self.

Do it only if absolutely necessary. It serves to create more generic decorators.

pyfunc
+5  A: 

I don't understand what you're coding for undoable -- that's not how decorators are normally coded and I don't know where that @decorator is coming from (is there a from youforgottotelluswhence import decorator or something even more evil? see why I can't stand the use of from to build "artificial barenames" instead of using nice decorated names?-).

With normal decorator coding, e.g....:

import functools

def undoable(f):
    @functools.wraps(f)
    def wrapper(self, *a, **k):
        tok = k.get('undo_token')
        if tok is None:
            tok = k['undo_token'] = self.get_undo_token()
        return f(self, *a, **k), tok
    return wrapper

there's absolutely no problem naming the wrapper's first, mandatory positional argument self, and much gain of clarity in using this rather than the less-readable args[0].

Alex Martelli
Then look it up: http://pypi.python.org/pypi/decorator/2.3.2
wheaties
+1 "I can't stand the use of `from` to build 'artificial barenames'"
Pete
Yep, that `from decorator import decorator` **really** does make the Q's code impossible to understand w/o a "reference URL", as such "artificial barenames" always do -- pity Michele chose to use it in his docs, giving users such a horrible example. Further pity that, in this use case at least, the third party decorator module mushes arguments up making code very hard to follow, while plain Python syntax neatly separates "the function being decorated" from "the arguments to the wrapper" -- I'd rather lose the black magic for "signature preservation" than lose code clarity by mushing 'em up.
Alex Martelli
Thank you. I've seen `@decorator` (from http://pypi.python.org/pypi/decorator/2.3.2) used quite a bit here, so I (erroneously) assumed it was best practice.
michael