在Python中使用reduce和装饰器的级联函数
我有一个叫做 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 个回答
你的 `cascade_modifier` 期望第一个参数是 `self`,那为什么需要它呢?你的装饰器并没有接收到你实际上需要装饰的函数对象。最重要的是,简化一下装饰器的概念:它接受一个函数,改变这个函数的行为,然后返回一个新的函数。所以,你不需要担心要把装饰器传给 `reduce`。只需使用普通的函数就可以了。
先暂时不考虑你在处理类方法的事情,咱们来聊聊你想做的事情的核心。
首先,你有这样的代码:
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中的单子变换器)。但我不太确定怎么实现这个。
为什么不让子类实现一个不同的方法,叫做 _render
呢?这样的话, render
就可以是
def render(self, *args, **kwargs):
for func in [c1, c2, c3, self._render]:
func(*args, **kwargs)
这样看起来更简单。
你遇到的问题是,你把新的 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