2

Since there is some behavior difference in map (especially with three arguments between Python 2 and Python - Python 2 vs Python 3 - Difference in map behavior with three arguments?), I was trying to be "safe" by using from past.builtins import map so that my functionality is intact. But it doesn't seem to be so?

Here is a Python 2 code:

map(lambda x: [x], [1, 2])

which gives:

[[1], [2]]

Here is the Python 3 code which I am expecting to behave the same way, but does not:

from past.builtins import map
map(lambda x: [x], [1, 2])

gives:

[1, 2]

Surprisingly the new map works as expected:

from builtins import map  # Not needed if you didn't evaluate the above code.
list(map(lambda x: [x], [1, 2]))

Is there a reason why past.builtins's map behaves like this? Is this a bug?


Looks like there are some problems in getting the Python 2 map behavior using past.builtins module as mentioned in the source code comments.

Nishant
  • 20,354
  • 18
  • 69
  • 101
  • 3
    TL;DR: you have found a bug in a [3rd party 0.x library](https://pypi.org/project/past/)…!? Perhaps reporting it to them would be best… – deceze Mar 12 '20 at 13:15
  • 1
    Somebody actually already did… https://github.com/PythonCharmers/python-future/issues/467 – deceze Mar 12 '20 at 13:18
  • I was assuming `future` and `past` modules to be extensions of standard library because it looked really similar to the `__future__` feature :(-. – Nishant Mar 12 '20 at 13:18

1 Answers1

2

This is an error in their implementation of map. Here is their code for it:

def oldmap(func, *iterables):
    """
    map(function, sequence[, sequence, ...]) -> list
    Return a list of the results of applying the function to the
    items of the argument sequence(s).  If more than one sequence is
    given, the function is called with an argument list consisting of
    the corresponding item of each sequence, substituting None for
    missing values when not all sequences have the same length.  If
    the function is None, return a list of the items of the sequence
    (or a list of tuples if more than one sequence).
    Test cases:
    >>> oldmap(None, 'hello world')
    ['h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd']
    >>> oldmap(None, range(4))
    [0, 1, 2, 3]
    More test cases are in past.tests.test_builtins.
    """
    zipped = itertools.zip_longest(*iterables)
    l = list(zipped)
    if len(l) == 0:
        return []
    if func is None:
        result = l
    else:
        result = list(starmap(func, l))

    # Inspect to see whether it's a simple sequence of tuples
    try:
        if max([len(item) for item in result]) == 1:
            return list(chain.from_iterable(result))
        # return list(flatmap(func, result))
    except TypeError as e:
        # Simple objects like ints have no len()
        pass
    return result

The error is where it says:

# Inspect to see whether it's a simple sequence of tuples

In their implementation, if the callable returns a list of objects with a len of one then those are "unpacked" and a flattened list is returned. I'm not sure where this comes from though, since, as far as I know, Python 2 did not do this, even for tuples:

# Python 2
print(map(lambda x: (x,), [1, 2]))
# [(1,), (2,)]

There seems to be an open issue about it in the library code repository, if you want to follow on that.

jdehesa
  • 58,456
  • 7
  • 77
  • 121
  • @jdesha, Do you mean we don't need that `try/except` block? – Nishant Mar 12 '20 at 13:38
  • 1
    @Nishant I think it would work as you expect if you remove it, for the given example. What I don't know is if there are cases were it is necessary. – jdehesa Mar 12 '20 at 13:39