Question

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?

Was it helpful?

Solution 2

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]

OTHER TIPS

In python2 it was possible to use tuple-unpacking for this:

>>> def func((a,b,c)):
...     return a+b+c
... 
>>> func((1,2,3))
6

However this feature was removed in python3. See PEP 3113. The reasons why it was removed are:

  • They are very difficult to introspect
  • They don't introduce any functionality since you can create a function like:

    def func(a_b_c):
       a,b,c = a_b_c
    

    And achieve the same result

  • They provided ugly error messages

With this removal python currently does not support what you want with any syntax nor any stdlib function.

Since this syntax was removed with a PEP I highly doubt that the core developers will accept your unpack_into function. However there may be a small chance to add something like that into the functools module which should contain this kind of things. You should probably ask to the python developers about this, but be sure to provide good arguments to support your request.

If foo is a function consisting of a single expression (i.e., can be written as a lambda), and it's a throwaway function so you don't need to save it for later, and you want to apply it to a list, don't use a function at all. Use a list comprehension:

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

This is more clear and more concise than anything involving a lambda and map, and eliminates the need for passing arguments at all.

More generally, if your function is a one-shot throwaway, it's not that important if it uses "ugly magic numbers" as in lambda t: t[0]*t[1]%t[2]. If you're willing to take time to make it look good, then you can also take time to write it as an actual function instead of cramming it into a lambda.

Yes of course, lambda is no different from any other function, e.g.:

lambda *args: pprint.pprint(args)
lambda *args: foo(args[0], args[7], args[-1])

and conversely:

l1 = lambda a, b, c: ...
l1(*range(3))

in addition, functools provides for partial evlauation:

import functools
foo = lambda a, b, c, d: None
part = functools.partial(foo, 1, 2)
part(3, 4)
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top