【问题标题】:Cascading functions in Python using reduce and decoratorsPython中使用reduce和decorator的级联函数
【发布时间】:2014-11-04 10:44:24
【问题描述】:

我有一个名为 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 中使用我尝试在此问题中解释的范例解决此问题的最佳方法是什么?

【问题讨论】:

  • ...你为什么要这样做?没有更简单的方法吗?而不是突变self.render 以对接子类(这对于可扩展性来说是一个真的 坏主意),你不能让子类实现,比如_render,它被a调用render 他们不会超载?或者你不能实现superrender 并调用它而不是render?你不能用子类继承他们的MyClass 子类并重载那里的行为吗?你正在做的事情对我来说似乎是最后的手段。

标签: python python-2.7 python-decorators method-resolution-order


【解决方案1】:

您遇到的麻烦是您将新的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 传递给可调用对象,并且没有实用的方法来使它们绑定方法,您可以做一些不同的事情,并使用封闭@ 中的self 参数987654333@范围:

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

【讨论】:

    【解决方案2】:

    为什么不让子类实现不同的方法_render?那么render就可以了

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

    这似乎更简单。

    【讨论】:

      【解决方案3】:

      让我们暂时忘记您正在处理 Class 方法这一事实,并谈谈您正在尝试做的事情的要点。

      首先,你有类似的东西

      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)
      

      等等。

      其中,前一个函数使下一个函数成为装饰器。问题是,如何阻止最后一个函数成为装饰器? 如果有某种 if ID 函数,这可能是可能的(类似于 Haskell 中的 monad 转换器)。但不确定如何实现这一目标。

      【讨论】:

      • 我认为您对“装饰器”的解释过于字面意思。提问者没有在任何地方使用 Python 的装饰器语法。我认为“装饰器”在这种情况下的意思只是一个作用于(并返回)其他函数的函数。
      【解决方案4】:

      您的 cascade_modifier 期望 self 作为第一个参数。你为什么需要它? 你的装饰器没有接收到你实际需要装饰的函数对象。 最重要的是 - 就它接受函数、改变其行为并返回新函数而言,它已经是一种装饰器。所以,你不需要担心装饰器通过减少。只需使用常规功能。

      【讨论】:

      • "你的 cascade_modifier 期望 self 作为第一个参数。你为什么需要它?" - 因为它是一个实例方法,而实例方法需要声明一个self参数,并且该参数是实际使用的,所以将其设为模块级函数并不会消除对该参数的要求。此外,reduce 不是装饰器,也不会从提供的缩减函数构建新函数。 (也就是说,寻找一个装饰器来传递给reduce 而不是其他类型的函数并不是一个好主意。)
      猜你喜欢
      • 2019-08-11
      • 1970-01-01
      • 1970-01-01
      • 2015-04-25
      • 2017-05-31
      • 2022-12-31
      • 1970-01-01
      • 2014-11-26
      • 2011-07-03
      相关资源
      最近更新 更多