the wolf the wolf - 3 months ago 8
Python Question

Python 3 vs Python 2 map behavior

In Python 2, a common (old, legacy) idiom is to use

map
to join iterators of uneven length using the form
map(None,iter,iter,...)
like so:

>>> map(None,xrange(5),xrange(10,12))
[(0, 10), (1, 11), (2, None), (3, None), (4, None)]


In Python 2, it is extended so that the longest iterator is the length of the returned list and if one is shorter than the other it is padded with
None
.

In Python 3, this is different. First, you cannot use
None
as an argument for the callable in position 1:

>>> list(map(None, range(5),range(10,12)))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'NoneType' object is not callable


OK -- I can fix that like so:

>>> def f(*x): return x
...
>>> list(map(f, *(range(5),range(10,12))))
[(0, 10), (1, 11)]


But now, I have a different problem:
map
returns the shortest iterator's length -- no longer padded with
None
.

As I port Python 2 code to Python 3, this is not a terrible rare idiom and I have not figured out an easy in place solution.

Unfortunately, the 2to3 tools does not pick this up -- unhelpfully suggesting:

-map(None,xrange(5),xrange(10,18))
+list(map(None,list(range(5)),list(range(10,18))))


Suggestions?




Edit

There is some discussion of how common this idiom is. See this SO post.

I am updating legacy code written when I was still in high school. Look at the 2003 Python tutorials being written and discussed by Raymond Hettinger with this specific behavior of map being pointed out...

Answer

I'll answer my own question this time.

With Python 3x, you can use itertools.zip_longest like so:

>>> list(map(lambda *a: a,*zip(*itertools.zip_longest(range(5),range(10,17)))))
[(0, 10), (1, 11), (2, 12), (3, 13), (4, 14), (None, 15), (None, 16)]

You can also roll ur own I suppose:

>>> def oldMapNone(*ells):
...     '''replace for map(None, ....), invalid in 3.0 :-( '''
...     lgst = max([len(e) for e in ells])
...     return list(zip(* [list(e) + [None] * (lgst - len(e)) for e in ells]))
... 
>>> oldMapNone(range(5),range(10,12),range(30,38))
[(0, 10, 30), (1, 11, 31), (2, None, 32), (3, None, 33), (4, None, 34), (None, None, 35), (None, None, 36), (None, None, 37)]