0

I have written several functions that run sequentially, each one taking as its input the output of the previous function so in order to run it, I have to run this line of code

make_list(cleanup(get_text(get_page(URL))))

and I just find that ugly and inefficient, is there a better way to do sequential function calls?

Devon M
  • 751
  • 2
  • 9
  • 24
  • merge multiple functions into one? Check out [Code smells](http://en.wikipedia.org/wiki/Code_smell). For more info we prob need more details on your functions. – C.B. Jan 08 '14 at 19:07
  • i may be an outlier, but i don't find that hard to understand. and since you do have to actually make all those function calls, any other method is going to introduce additional overhead, however small, so i don't think any other way would be more efficient either. however, it's a little harder to debug, given all the intermediate temporaries.... – Corley Brigman Jan 08 '14 at 19:21

4 Answers4

5

Really, this is the same as any case where you want to refactor commonly-used complex expressions or statements: just turn the expression or statement into a function. The fact that your expression happens to be a composition of function calls doesn't make any difference (but see below).

So, the obvious thing to do is to write a wrapper function that composes the functions together in one place, so everywhere else you can make a simple call to the wrapper:

def get_page_list(url):
    return make_list(cleanup(get_text(get_page(url))))

things = get_page_list(url)
stuff = get_page_list(another_url)
spam = get_page_list(eggs)

If you don't always call the exact same chain of functions, you can always factor out into the pieces that you frequently call. For example:

def get_clean_text(page):
    return cleanup(get_text(page))
def get_clean_page(url):
    return get_clean_text(get_page(url))

This refactoring also opens the door to making the code a bit more verbose but a lot easier to debug, since it only appears once instead of multiple times:

def get_page_list(url):
    page = get_page(url)
    text = get_text(page)
    cleantext = cleanup(text)
    return make_list(cleantext)

If you find yourself needing to do exactly this kind of refactoring of composed functions very often, you can always write a helper that generates the refactored functions. For example:

def compose1(*funcs):
    @wraps(funcs[0])
    def composed(arg):
        for func in reversed(funcs):
            arg = func(arg)
        return arg
    return composed

get_page_list = compose1(make_list, cleanup, get_text, get_page)

If you want a more complicated compose function (that, e.g., allows passing multiple args/return values around), it can get a bit complicated to design, so you might want to look around on PyPI and ActiveState for the various existing implementations.

abarnert
  • 354,177
  • 51
  • 601
  • 671
4

You could try something like this. I always like separating train wrecks(the book "Clean Code" calls those nested functions train wrecks). This is easier to read and debug. Remember you probably spend twice as long reading your code than writing it so make it easier to read. You will thank yourself later.

url = get_page(URL)
url_text = get_text(url)
make_list(cleanup(url_text))

# you can also encapsulate that into its own function
def build_page_list_from_url(url):
    url = get_page(URL)
    url_text = get_text(url)
    return make_list(cleanup(url_text))
jramirez
  • 8,537
  • 7
  • 33
  • 46
0

Options:

  1. Refactor: implement this series of function calls as one, aptly-named method.
  2. Look into decorators. They're syntactic sugar for 'chaining' functions in this way. E.g. implement cleanup and make_list as a decorators, then decorate get_text with them.
  3. Compose the functions. See code in this answer.
Community
  • 1
  • 1
Keeler
  • 2,102
  • 14
  • 20
0

You could shorten constructs like that with something like the following:

class ChainCalls(object):
    def __init__(self, *funcs):
        self.funcs = funcs
    def __call__(self, *args, **kwargs):
        result = self.funcs[-1](*args, **kwargs)
        for func in self.funcs[-2::-1]:
            result = func(result)
        return result

def make_list(arg): return 'make_list(%s)' % arg
def cleanup(arg):   return 'cleanup(%s)' % arg
def get_text(arg):  return 'get_text(%s)' % arg
def get_page(arg):  return 'get_page(%r)' % arg

mychain = ChainCalls(make_list, cleanup, get_text, get_page)

print( mychain('http://is.gd') )

Output:

make_list(cleanup(get_text(get_page('http://is.gd'))))
martineau
  • 119,623
  • 25
  • 170
  • 301