在Python中使用reduce和装饰器的级联函数

2 投票
4 回答
2697 浏览
提问于 2025-04-20 22:23

我有一个叫做 render 的方法,这个方法实际上是由一个子类来实现的,调用方式如下:

class MyClass(object):

    def __call__(self, *args, **kwargs):

        api_response = self.render(*args, **kwargs)

        return api_response

    def render(self, *args, **kwargs):
        """ This method is implemented by a subclass."""    
        raise NotImplementedError

    def cascade_callables():
        print 'Ask awesome people at stackoverflow for the solution to this problem.'

我有一个可调用的列表 [c1, c2, c3]。我想在 cascade_callables 方法里面做一些类似这样的事情:

def cascade_callables():
    callables = [c1, c2, c3]
    callables.append(self.render)
    self.render = reduce(some_decorator_that_accepts_two_functions, callables)

所以,基本上,我想让我的 render 方法像这样工作,但不想修改实际的实现:

c1(*args, **kwargs)
c2(*args, **kwargs)
c3(*args, **kwargs)
render(*args, **kwargs)

我尝试了类似这样的东西,想用作装饰器,这样我就可以在 reduce 中使用:

def cascade_modifiers(modifier1, modifier2):

    def cascaded_modifier(self, *args, **kwargs):

        modifier1(self, *args, **kwargs)
        modifier2(self, *args, **kwargs)

    return cascaded_modifier

但是我得到了这个结果:

TypeError: cascaded_modifier() takes at least 1 argument (0 given)

在 Python 2.7 中,解决这个问题的最佳方法是什么?我在这个问题中尝试解释的思路是怎样的?

4 个回答

-2

你的 `cascade_modifier` 期望第一个参数是 `self`,那为什么需要它呢?你的装饰器并没有接收到你实际上需要装饰的函数对象。最重要的是,简化一下装饰器的概念:它接受一个函数,改变这个函数的行为,然后返回一个新的函数。所以,你不需要担心要把装饰器传给 `reduce`。只需使用普通的函数就可以了。

-1

先暂时不考虑你在处理类方法的事情,咱们来聊聊你想做的事情的核心。

首先,你有这样的代码:

c1(*args, **kwargs)
c2(*args, **kwargs)
c3(*args, **kwargs)
render(*args, **kwargs) # <- the decorator should not be placed here

@render(*args, **kwargs) # <- it should be placed here
c1(*args, **kwargs)
c2(*args, **kwargs)
c3(*args, **kwargs)

你想要的是,上面的代码应该变成:

@renderc1(*args, **kwargs)
c2(*args, **kwargs)
c3(*args, **kwargs)

然后变成:

@renderc1c2(*args, **kwargs)
c3(*args, **kwargs)

接着再变成:

@renderc1c2(*args, **kwargs)
c3(*args, **kwargs)

记住,@符号只是一个语法上的简化,实际上是:

@render(*args, **kwargs) # <- it should be placed here
c1(*args, **kwargs)

变成:

现在我们把其他的函数放进去:

@render(*args, **kwargs) # <- it should be placed here
c1(*args, **kwargs)
c2(*args, **kwargs)
c3(*args, **kwargs)

变成:

c1 = render(c1)
c2(*args, **kwargs)
c3(*args, **kwargs)

这就没有意义了。

你想要的是:

@render(*args, **kwargs) # <- it should be placed here
c1(*args, **kwargs)
@c1
c2(*args, **kwargs)
@c2
c3(*args, **kwargs)

这大致可以翻译成:

c1 = render(c1)
c2 = c1(c2)
c3 = c2(c3)

等等。

在这里,前一个函数让下一个函数成为一个装饰器。问题是,如何让最后一个函数不变成装饰器呢?如果有某种ID函数,这可能是可行的(就像Haskell中的单子变换器)。但我不太确定怎么实现这个。

0

为什么不让子类实现一个不同的方法,叫做 _render 呢?这样的话, render 就可以是

def render(self, *args, **kwargs):
    for func in [c1, c2, c3, self._render]:
        func(*args, **kwargs)

这样看起来更简单。

2

你遇到的问题是,你把新的 render 方法保存在了一个实例变量里。这意味着它不会自动接收到 self,因为在Python中,方法的绑定是通过描述符协议来实现的,而描述符只有在类变量中才能正常工作。

所以,你可能需要确保所有可调用的对象(如果需要使用 self)都是已经绑定的,并且要重写 cascaded_modifier,让它不再期待有 self 这个参数。实际上,你已经在传递一个绑定的原始 render 函数版本,所以在这种情况下,你没有得到第二个 self 是件好事!

注意,如果你使用循环而不是 reduce,可以简化 cascade_callables。这种方法需要的函数会少一个:

def cascade_callables(self):
    callables = [c1, c2, c3]   # these should be bound methods if they need self
    callables.append(self.render)

    def new_render(*args, **kwargs): # no self parameter here
        for c in callables:
            c(*args, **kwargs)       # nor here

    self.render = new_render

如果出于某种原因你确实需要把 self 传递给可调用对象,而又没有实际的方法让它们成为绑定的方法,你可以稍微改变一下做法,使用来自外部 cascade_callables 作用域的 self 参数:

def cascade_callables(self):
    callables = [c1, c2, c3]   # if these are not bound, we can work around the issue
    old_render = self.render   # this one is bound though so we can't mix it in

    def new_render(*args, **kwargs): # no self parameter here
        for c in callables:
            c(self, *args, **kwargs) # but here we access the enclosing scope's self
        old_render(*args, **kwargs)  # this needs to be called separately in this version

    self.render = new_render

撰写回答