Reputation: 35572
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...
Upvotes: 19
Views: 10315
Reputation: 5693
One way if you need some obsolete functionality in from the Python 2 then one way - write it yourself. Of course, it's not built-in functionality, but at least it is something.
The code snippet below takes 27 lines
#!/usr/bin/env python3
def fetch(sequence, index):
return None if len(sequence) <= index else sequence[index]
def mymap(f, *args):
max_len = 0
for i in range(len(args)):
max_len = max(max_len, len(args[i]))
out = []
for i in range(max_len):
t = []
for j in range(len(args)):
t.append(fetch(args[j],i))
if f != None:
# Use * for unpack arguments from Arbitarily argument list
# Use ** for unpack arguments from Keyword argument list
out.append(f(*t))
else:
out.append(tuple(t))
return out
if __name__ == '__main__':
print(mymap(None, [1,2,3,4,5],[2,1,3,4],[3,4]))
print(mymap(None,range(5),range(10,12)))
Upvotes: 1
Reputation: 11
you can solve the problem like this:
list(map(lambda x, y: (x, y),[1, 2, 3 ,4, 5], [6, 7, 8, 9, 10]))
Upvotes: -1
Reputation: 35572
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)]
Upvotes: 2
Reputation: 28856
itertools.zip_longest
does what you want, with a more comprehensible name. :)
Upvotes: 18