Search code examples
pythonpython-3.xlambdaiterableenumerate

Why `for` can iterate pairs, but `lambda i,x:` cannot `map` pairs?


I could not find such a question in the archive.

With for is possible to unpack iterated pairs:

>>> for i, x in enumerate([5,10,15]): print('x[%d]=%d' % (i, x))
x[0]=5
x[1]=10
x[2]=15

or also is possible to not unpack them:

>>> for x in enumerate([5,10,15]): print(x)
(0, 5)
(1, 10)
(2, 15)

However, when using map(), this unpack is not possible:

>>> list(map(lambda i, x: x**i, enumerate([5,10,15])))
Traceback (most recent call last):
  File "<input>", line 1, in <module>
TypeError: <lambda>() missing 1 required positional argument: 'x'

Why is this? Design choice? And why?

I guess one could implement such a wrapper:

def map1(func, *iterables):
    from inspect import signature
    params = signature(func).parameters
    if len(params) == 1:
        yield from map(func, *iterables)
    else:
        yield from map(lambda x: func(*x), *iterables)

(works in every case)

or this other one, that blindly unpacks arguments:

def map2(func, *iterables):
    yield from map(lambda x: func(*x), *iterables)

(I guess the latter is going to cause some trouble)

Is there a (good) reason to not do it?


Solution

  • This is not an issue with lambda but with map. The documentation says:

    If additional iterable arguments are passed, function must take that many arguments and is applied to the items from all iterables in parallel.

    In other words, the function must take the same number of arguments as the number of iterables that you pass. The size of each individual element in the iterables is not relevant.

    If you want to pass each element of a single iterable as multiple arguments, you can use zip and argument unpacking to transpose the sequence:

    >>> map(lambda i, x: x**i, *zip(*enumerate([5,10,15])))
    [1, 10, 225]
    

    As Ashwini noted, you can also use itertools.starmap instead of map.