Python设计问题(在这种情况下可以/应该使用装饰器吗?)

时间:2010-08-13 03:46:47

标签: python decorator

我有一个问题可以简化如下:我有一组特定的对象,我想以特定的方式修改。因此,我可以编写一个修改单个对象的函数,然后创建一个将该函数应用于集合中所有对象的装饰器。

所以,我想我有类似的东西:

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)

但是,有时我可能只想对一个对象进行操作。

有没有办法以编程方式“解开”modify函数来重新获得原始(单个对象)函数? (我的意思是,不仅仅是删除装饰器。)或者是否有另一种方法可以更好地用于这种情况?

为了清晰和完整,实际 modify_all装饰器中的内容比此处所示的要多得多。 modifymodify_all都有一定的工作要做,这就是为什么我认为装饰者可能会很好。此外,我还有modify的其他变体可以直接从modify_all中受益,这使它更有用。但我有时需要使用原始的modify函数来做事情。我知道我总是可以传递一个单元素集来“欺骗”它按原样工作,但我想知道是否有更好的方法或更好的设计。

5 个答案:

答案 0 :(得分:2)

手动将装饰器应用于该功能一次,并将结果保存为新名称。

def modify_one(obj):
    ...

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

答案 1 :(得分:2)

装饰器适用于以特定形式应用高阶函数(HOF)

def f ...

f = HOF(f)

在这种情况下,语法(具有相同的语义)

@HOF
def f ...

更简洁,并立即警告读者f永远不会被“裸露”使用。

对于你的用例,你需要“裸f”和“装饰f”,它们必须有两个不同的名称,因此装饰器语法不能立即应用 - 但它们都不是必需的!一个很好的选择可能是将装饰名称用作装饰函数的属性,例如:

def modify(obj): ...

modify.all = modify_all(modify)

现在,您可以随时致电modify(justone)modify.all(allofthem)并快速编码。

答案 2 :(得分:1)

亚历克斯已经发布了类似的东西,但这是我的第一个想法,所以我会继续发布它,因为它确实避开了迈克格雷厄姆对它的反对意见。 (即使我不同意他的反对意见:人们不了解某事的事实是不使用它的理由。这就是人们继续不知道它)

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)

您只需致电undecorate即可访问已修饰的功能。

另一种选择是像这样处理它:

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

答案 3 :(得分:0)

您可以使用装饰器来应用Alex的想法

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

或许foreach是一个更好的名字

答案 4 :(得分:-1)

在您的特定情况下,我会考虑使用参数解包。只需稍微修改现有代码即可完成此操作:

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)

但是,参数解包的操作确实需要一些时间,所以这可能比刚刚通过普通旧列表的原始版本稍慢一些。如果您担心这会使您的应用程序放慢太多,请尝试一下,看看它有什么不同。

根据您使用的对象类型,另一个选项是让装饰器“智能地”决定它是否应该使用循环:

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)

但是,当你这样做时,有一些混淆的可能性,如果你希望它作为一个对象在列表上运行时,如果你曾用列表调用modify,那么它显然不起作用。 (换句话说,如果my_obj可能是一个列表,请不要这样做)