Python: 类似jQuery的函数链式调用?

9

我在Google上没有找到关于这个主题的任何内容,所以我想我应该在这里提问:

是否可以像jQuery一样使用Python链式函数?

['my', 'list'].foo1(arg1, arg2).foo2(arg1, arg2).foo3(arg1, arg2) #etc...

当我写下这段代码时,我失去了很多空间和可读性:

foo3(foo2(foo1(['my', 'list'], arg1, arg2), arg1, arg2), arg1, arg2) #etc...

似乎存在一些神秘的库来创建这样的函数,但我似乎看不出为什么这必须看起来如此复杂...

谢谢!


术语是“流畅接口”。不知道为什么GvR不喜欢它们。 - Ignacio Vazquez-Abrams
5个回答

11
只要函数返回一个值,就可以链式调用。在jQuery 中,选择器方法通常返回选择器本身,这就是让你可以进行链式调用的原因。如果你想在 Python 中实现链式调用,可以像这样做:
class RoboPuppy:

  def bark(self):
    print "Yip!"
    return self

  def growl(self):
    print "Grr!"
    return self

pup = RoboPuppy()
pup.bark().growl().bark()  # Yip! Grr! Yip!

然而,你的问题似乎是函数参数太过拥挤。这种情况下链式编程并不是解决方案。如果你想要压缩函数参数,只需在将它们传递给函数之前将它们赋值给变量,就像这样:

spam = foo(arg1, arg2)
eggs = bar(spam, arg1, arg2)
ham = foobar(eggs, args)

谢谢你!我都不知道我的类还能做那种事情呢!对于那种类型的函数,它表现得非常好,但是我好像无法将一个函数的返回值传递给另一个函数... - Blender
这似乎符合这里的示例:http://en.wikipedia.org/wiki/Fluent_interface - Ishpeck

10

这是对Simon的ListMutator建议的扩展:

class ListMutator(object):

    def __init__(self, seq):
        self.data = seq

    def foo1(self, arg1, arg2):
        self.data = [x + arg1 for x in self.data]
        # This allows chaining:
        return self

    def foo2(self, arg1, arg2):
        self.data = [x*arg1 for x in self.data]
        return self

if __name__ == "__main__":
    lm = ListMutator([1,2,3,4])
    lm.foo1(2, 0).foo2(10, 0)
    print lm.data

    # Or, if you really must:
    print ListMutator([1,2,3,4]).foo1(2, 0).foo2(10, 0).data
你可以更进一步,通过使用collections抽象基类,使ListMutator完全像一个列表。实际上,你甚至可以去继承list,但这可能会限制你做一些你可能需要做的事情... 而关于继承内置类型如list的一般意见我不太清楚。

2
仅返回 self 的问题在于,如果将其分配给另一个变量,则它只是 Python 中的引用。在您的示例中,如果我们添加 lm_two = lm 并再次执行 lm.foo1(2, 0).foo2(10, 0),则会更改两个对象(lmlm_two)。最好返回 self.__class__(self.data) - ucyo

3
如果我们谈论对象方法,那么很简单,只需从每个方法中return self。另一方面,如果您想链接未绑定的函数,那么像您想要的方式链接它们对我来说并没有太多意义。当然,它看起来很好,但从语义上讲是不连贯的,因为“.”代表对象属性访问,而不是“链”。

哦,你的意思是我不能像这样定义函数?我想我得用jQuery来定义一个L('my', 'strange', 'list')类型的函数。谢谢! - Blender
作为一个离题的问题,我可以在Python对象名称中使用哪些特殊字符?我尝试了 $,但解释器不喜欢那个... - Blender
从技术上讲,你可能可以这样做,因为在Python中函数也是对象。一个函数可以有一个自定义属性,它也是一个函数,可以像func1.func2(args)一样被调用。但这会很混乱。 - Simon
从你的例子中,看起来你正在多步骤地改变一个列表。为什么不按照Pythonic的方式,创建一个ListMutator类,将所有需要的函数作为方法呢? - Simon
我有一个类似于已经存在的类,但我必须初始化一个对象o并像这样嵌套函数:o.foo(o.foo2(['the', 'list'])),但我知道我正在做某些错误的事情,因为这比仅使用普通函数更糟糕... - Blender

1

供将来参考:查看一下 Moka,这是一个极简的函数式编程库。以下是他们的例子:

(List()                    # Create a new instance of moka.List
   .extend(range(1,20))    # Insert the numbers from 1 to 20
   .keep(lambda x: x > 5)  # Keep only the numbers bigger than 5
   .rem(operator.gt, 7)    # Remove the numbers bigger than 7 using partial application
   .rem(eq=6)              # Remove the number 6 using the 'operator shortcut'
   .map(str)               # Call str on each numbers (Creating a list of string)
   .invoke('zfill', 3)     # Call zfill(x, 3) on each string (Filling some 0 on the left)
   .insert(0, 'I am')      # Insert the string 'I am' at the head of the list
   .join(' '))             # Joining every string of the list and separate them with a space.

>>> 'I am 007'

0

看看this。它是一个简单的包装类,用于链接。它实现了一些underscore.js库的功能。你可以用underscore来包装你的列表、元组或字典,然后玩弄它,最后通过添加另一个underscore来获取值。

print (_([1,2,3])
       .map(lambda x: x+1)
       .reverse()
       .dict_keys(["a", "b", "c"])
       .invert()
       .items()
       .append(("this is what you got", "chaining"))
       .dict()._)

输出:

{2: 'c', 3: 'b', 4: 'a', 'this is what you got': 'chaining'}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接