开发者

Python design question (Can/should decorators be used in this case?)

I have a problem that can be simplified as follows: I have a particular 开发者_JAVA技巧set of objects that I want to modify in a particular way. So, it's possible for me to write a function that modifies a single object and then create a decorator that applies that function to all of the objects in the set.

So, let's suppose I have something like this:

def modify_all(f):
    def fun(objs):
        for o in objs:
            f(o)

@modify_all
def modify(obj):
    # Modify obj in some way.

modify(all_my_objs)

However, there may also be times when I just want to operate on one object by itself.

Is there a way to "undecorate" the modify function programmatically to get the original (single object) function back again? (Without just removing the decorator, I mean.) Or is there another approach that would be better to use in such a case?

Just for clarity and completeness, there's quite a bit more going on in the actual modify_all decorator than is illustrated here. Both modify and modify_all have a certain amount of work to carry out, which is why I thought the decorator might be nice. Additionally, I have other variants of modify that can directly benefit from modify_all, which makes it even more useful. But I do sometimes need to do things using the original modify function. I know that I can always pass in a one-element set to "trick" it into working as-is, but I'm wondering if there's a better way or a better design for the situation.


Apply the decorator manually to the function once and save the result in a new name.

def modify_one(obj):
    ...

modify_some = modify_all(modify_one)
modify_some([a, b, c])
modify_one(d)


Decorators are meant for when you apply a higher-order function (HOF) in the specific form

def f ...

f = HOF(f)

In this case, the syntax (with identical semantics)

@HOF
def f ...

is more concise, and immediately warns the reader that f will never be used "bare".

For your use case, where you need both "bare f" and "decorated f", they'll have to have two distinct names, so the decorator syntax is not immediately applicable -- but neither is it at all necessary! An excellent alternative might be to use as the decorated name an attribute of the decorated function, for example:

def modify(obj): ...

modify.all = modify_all(modify)

Now, you can call modify(justone) and modify.all(allofthem) and code happily ever after.


Alex had already posted something similar but it was my first thought too so I'll go ahead and post it because it does sidestep Mike Grahams objection to it. (even if I don't really agree with his objection: the fact that people don't know about something is not a reason to not use it. This is how people continue to not know about it)

def modify_all(f):
    def fun(objs):
        for o in objs:
            f(o)
    fun.unmodified = f
    return fun

def undecorate(f):
    return f.unmodified

@modify_all
def modify(obj):
    # Modify obj in some way.

modify(all_my_objs)
undecorate(modify)(one_obj)

You could just call undecorate to access the decorated function.

another alternative would be to handle it like this:

def modify_one(modify, one):
    return modify.unmodified(one)


You can use a decorator to apply Alex's idea

>>> def forall(f):
...     def fun(objs):
...         for o in objs:
...             f(o)
...     f.forall=fun
...     return f
... 
>>> @forall
... def modify(obj):
...     print "modified ", obj
... 
>>> modify("foobar")
modified  foobar
>>> modify.forall("foobar")
modified  f
modified  o
modified  o
modified  b
modified  a
modified  r

Perhaps foreach is a better name


In your particular case I'd consider using argument unpacking. This can be done with only a slight modification of your existing code:

def broadcast(f):
    def fun(*objs):
        for o in objs:
            f(o)
    return fun

@broadcast
def modify(obj):
    # Modify obj in some way.

modify(*all_my_objs)
modify(my_obj)

However, the operation of argument unpacking does take a bit of time, so this will probably be slightly slower than the original version that just passes a plain old list. If you're concerned about this slowing down your application too much, try it out and see what kind of a difference it makes.

Depending on what kind of objects you work with, another option is to make the decorator "intelligently" decide whether or not it should use a loop:

def opt_broadcast(f):
    def fun(obj):
        if isinstance(obj, list):
            for o in objs:
                f(o)
        else:
            f(o)
    return fun

@opt_broadcast
def modify(obj):
    # Modify obj in some way.

modify(all_my_objs)
modify(my_obj)

There's some potential for confusion when you do this, though, and obviously it doesn't work if you would ever be calling modify with a list when you want it to operate on the list as a single object. (In other words, if my_obj could ever be a list, don't do this)

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜