How to pass a list as argument to map?

I don’t want to iterate the list argument. But, the following syntax iterates the list argument. How to not iterate the second list?

def test(myVal, myList):
    return myVal * len(myList)


list1 = [5, 2, 7, 6, 1]
list2 = ['a', 'b', 'c']
result = map(test, list1, [list2])
resultList = list(result)
print(resultList)

I expect
[15, 6, 21, 18, 1]
from the test operations
[5*3, 2*3, 7*3, 6*3, 1*1]

But instead, i just get
[15]

map with multiple iterables always iterates all of them in parallel and stops when the shortest iterable is exhausted. So,

map(test, list1, [list2])

will compute test(list1[0], list2) and then immediately stop, since [list2] only contains one entry.

Depending on your use case, there’s a few ways you could go about handling this.

1. Extend the arguments to map

As noted, map terminates because [list2] only contains one entry. You can band-aid that by instead passing an iterable with many duplicate entries of list2 (at least as long as list1, or possibly infinitely long):

>>> list(map(test, list1, [list2] * len(list1)))
[15, 6, 21, 18, 3]

>>> import itertools
>>> list(map(test, list1, itertools.repeat(list2)))
[15, 6, 21, 18, 3]

2. Partially apply test ahead of time

If you always want to pass the same list as the second argument to test, you can instead create a new version of the test that pre-supplies list2 as the argument for myList. That can be done either by defining a new function or using functools.partial:

import functools

def test2(myVal):
    return test(myVal, list2)

test3 = functools.partial(test, myList=list2)

Both can be used in the same way:

>>> list(map(test2, list1))
[15, 6, 21, 18, 3]
>>> list(map(test3, list1))
[15, 6, 21, 18, 3]

So, it can’t be done. Wow. That seems inconsistent. There should be a way to distinguish the map’s iterable from an argument to the function.

Your suggestion to put my operation into the map statement, instead of keeping it inside the function where it belongs, defeats the purpose of creating a function.

My use of the same value len(myList) is just an example, to show passing a list.

Passing an iterable with many duplicate entries of list2 seems an inefficient use of memory.

My solution is list comprehension style, which altho less elegant than map, keeps the operation inside the function.

result = [test(testVal, list2) for testVal in list1]

All arguments after the first function are always iterated through. There is nothing inconsistent here: you can’t pass non-list arguments via map either. If you think there is something inconsistent about the way list is being treated, please show code where another type would work as you expect.

It’s not. It is just an iterable that yields the same object over and over. You can test like so:

>>> a = [1, 2, 3]
>>> repeated = itertools.repeat(a)
>>> next(repeated).append(4)
>>> a
[1, 2, 3, 4]

You can write a map that works that way:

def johns_map(func, iterable, *args):
    for elem in iterable:
        yield func(elem, *args)

Myself, I think comprehensions are often more elegant and readable than map().

1 Like

i can’t? :thinking:

Non-iterable in the general case, yes. Try out whatever syntax you thought would work and report back.

i can’t tell if you’re saying i can or cannot pass scalars.

You indeed cannot pass scalars, because the entire point of map is to iterate over whatever it was given. It will not special-case scalars (for example, by repeating them for each value in the other iterables). As we say, “special cases aren’t special enough to break the rules”.

2 Likes

However, it’s not hard to do that with itertools.repeat if that’s what you want.

1 Like

The point of a function is to receive arguments. I would think it would make total sense to accommodate iterables and arguments. I see no contradiction there.

But it don’t, so nothing more to be said.

I like @effigies suggestion to roll my own map fx.

Thx!