Search code examples
pythonlambdacoding-stylelanguage-designiterable-unpacking

Python: Is there syntax-level support for unpacking, from tuples, the arguments to an *anonymous* function?


Suppose we have the following:

args = (4,7,5)
def foo(a,b,c): return a*b%c

Python conveniently allows tuple unpacking:

foo(4,7,5)             # returns 3
foo(*args)             # returns foo(4,7,5), i.e. 3

So that we don't have to do this:

foo(t[0], t[1], t[2])  # a repulsive, verbose, and error-prone synonym

Now suppose we had a list of similar 3-tuples and wanted a list of foo(t) for each tuple t. There is "one obvious way to do it":

list(map(lambda t: foo(*t), listoftuples))

But now suppose foo is just a throw-away function. We don't want rubbish polluting our namespace. Let's sweep it under the rug of anonymity!:

list(map(lambda t: (lambda a, b, c: a*b%c)(*t), listoftuples))

Well, we now have nested lambdas. Sure, we can parse that. But we run the risk of being mistaken for a schemer who delights in constructing cryptic spells for the sole purpose of stumping those presumptuous enough to review our code.

Furthermore, this is kinda verbose for such a simple idea. This just does not seem pythonic. (In scala, the equivalent of that inner lambda is (_*_%_), assuming context allows type inference. If this was pythonic, wouldn't it be similarly concise?).

We could remove that inner lambda this way:

list(map((lambda t: t[0] * t[1] % t[2]), listoftuples))

That's shorter, but repulsive. I have found that using magic numbers (rather than names) to refer to parameters tends to cause errors.

It would be great if it looked much more like this:

list(map((lambda a, b, c: a*b%c), listoftuples))

Of course, it couldn't be that. That's like trying to call foo(args). We need an asterisk, so to speak. Here's one possible asterisk:

def unpackInto(func): return lambda t: func(*t)

It makes for pleasantly readable code:

list(map(unpackInto(lambda a, b, c: a*b%c), listoftuples))

But we'd have to import that from a personal module all the time. That's not suitable for collaboration, and it's kind of annoying for one-time use.

TL;DR

I want unpackInto to be part of the language. Is it already supported in syntax? In standard libraries?


Solution

  • starmap! It's in the itertools package.

    From my examples:

    list(map(lambda t: foo(*t), listoftuples))
    

    becomes

    list(starmap(foo, listoftuples))
    

    See why it's called starmap? And with anonymous functions:

    def unpackInto(func): return lambda t: func(*t)
    list(map(unpackInto(lambda a, b, c: a*b%c), listoftuples))
    

    becomes

    list(starmap(lambda a, b, c: a*b%c, listoftuples))
    

    A simple four-letter prefix, star, is the "asterisk" I was looking for.

    So, yes, there is standard library support for unpacking parameters from tuples into anonymous functions but only for map, via starmap.

    No, there is no syntax-level support, but there was syntax level support in python 2. See Bakuriu's answer. The last would become:

    list(map(lambda (a, b, c): a*b%c, listoftuples))
    

    Which is even more concise. It's almost a shame it had to be taken away.

    Then again, lambda is rarely as concise or readable as a list comprehension:

    [a*b%c for a, b, c in listoftuples]