9

I couldn't find anything on this subject on Google, so I think I should ask it here:

Is it possible to chain functions with Python, like jQuery does?

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

I am losing a lot of space and readability when I write this code:

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

There seems to exist some illusive library for creating such functions, but I can't seem to see why this has to be so complicated-looking...

Thanks!

Blender
  • 289,723
  • 53
  • 439
  • 496

5 Answers5

11

As long as the function returns a value, you can chain it. In jQuery, a selector method usually returns the selector itself, which is what allows you to do the chaining. If you want to implement chaining in python, you could do something like this:

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!

Your problem, however, seems to be that your function arguments are too cramped. Chaining is not a solution to this. If you want to condense your function arguments, just assign the arguments to variables before passing them to the function, like this:

spam = foo(arg1, arg2)
eggs = bar(spam, arg1, arg2)
ham = foobar(eggs, args)
camel_space
  • 1,057
  • 1
  • 7
  • 10
  • :D Thank you very much! I did not even know that my class could do that! It works well for functions of that type, but I can't seem to be able to pipe the return of one function into another... – Blender Dec 03 '10 at 06:45
  • This appears to fall in line with the examples here: http://en.wikipedia.org/wiki/Fluent_interface – Ishpeck Dec 03 '10 at 06:51
10

Here's an expansion of Simon's ListMutator suggestion:

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

You could go one better and make ListMutator act entirely like a list by using the collections abstract base classes. In fact, you could subclass list itself, although it may restrict you from doing certain things you might need to do... and I don't know what the general opinion is on subclassing built-in types like list.

detly
  • 29,332
  • 18
  • 93
  • 152
  • 2
    The problem with returning just `self` is, that it is just a reference in Python if you assign it to a different variable. In your example. If we add `lm_two = lm` and do `lm.foo1(2, 0).foo2(10, 0)` again, we change both (`lm` and `lm_two`. It is better to `return self.__class__(self.data)`. – ucyo Jan 12 '16 at 16:22
3

If we're talking about object methods, then it's trivial, just return self from every method. On the other hand, if you would like to chain unbound functions, it doesn't really make sense to me to chain them the way you want to. Sure, it looks nice, however it's semantically incoherent because the "." stands for object attribute access and not for "chain".

Wylie Kulik
  • 696
  • 5
  • 17
Simon
  • 12,018
  • 4
  • 34
  • 39
  • Hmm, so you're saying that I *can't* define functions like this? I think I would have to go jQuery on it and define myself a `L('my', 'strange', 'list')` type function. Thanks! – Blender Dec 03 '10 at 06:32
  • Just as a sort of off-topic question, what special characters can I use in an object name in Python? I tried `$`, but the interpreter doesn't like that... – Blender Dec 03 '10 at 06:33
  • Well, technically you probably could since functions are also objects in Python. A function can have a custom attribute which is also a function and which can be called like `func1.func2(args)`. But it would be a mess. – Simon Dec 03 '10 at 06:35
  • From your example, it looks like you're mutating a list in a number of steps. Why don't you go the pythonic way and make a ListMutator class which has all the function you need as methods? – Simon Dec 03 '10 at 06:37
  • I have a class similar to that already in place, but I have to initialize an object `o` and nest the functions like this: `o.foo(o.foo2(['the', 'list']))`, but I *know* I'm doing something wrong, as this is worse than just plain functions... – Blender Dec 03 '10 at 06:41
1

For future reference: have a look at Moka, a minimalist functional programming library. From their examples:

(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'
Manuel Ebert
  • 8,429
  • 4
  • 40
  • 61
0

Take a look at this. It is A simple wrapper class for chaining. And it implemented some of the underscore.js lib's functionality. You wrap your list, tuple or dict with an underscore, and play with it then get the value out of it by appending another 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()._)

output:

{2: 'c', 3: 'b', 4: 'a', 'this is what you got': 'chaining'}
wenjun.yan
  • 618
  • 9
  • 15