21

I wanted to calculate the sum of squares up to n. Say n is 4. Then this code generates a list a map object in the range 0 to 4:

m = map(lambda x: x**2, range(0,4))

Ease enough. Now call list on m, and then sum:

>>> sum(list(m))
14

The unexpected behavior is that if I run the last line again, the sum is 0:

>>> sum(list(m))
0

I suspect that this is because calling list(m) returns an empty list, but I can't find an explanation for this behavior. Can someone help me out with this?

wjandrea
  • 28,235
  • 9
  • 60
  • 81
ADB
  • 1,210
  • 4
  • 15
  • 23
  • 2
    `map` is an *iterator*, which is consumed by the first `list` call. Why don't you assign `list(m)` and reuse that? – jonrsharpe Apr 07 '16 at 20:53
  • @jonrsharpe Yes, I see that is an alternative, but I wanted to know why this happens. So you're saying that list() consumes the iterator? Can you tell me more (or where to find out more)? – ADB Apr 07 '16 at 20:55
  • 2
    Python tutorials/documentation? Google search for *"python iterator"*? – jonrsharpe Apr 07 '16 at 20:56
  • Would be better to say that the list call consumes the _generator_. – ADB Apr 08 '16 at 06:57
  • 1
    No, it wouldn't: https://docs.python.org/3.0/whatsnew/3.0.html#views-and-iterators-instead-of-lists – jonrsharpe Apr 08 '16 at 07:00
  • "map(_function, iterable,_ ...) Return an iterator that applies _function_ to every item of _iterable_, yielding the results" (https://docs.python.org/3.0/library/functions.html#map) So I believe the 'yielding' is what tripped me up here... – ADB Apr 19 '16 at 16:02
  • Also type(m) returns . This seems to conflict with the docs, which state that map() returns an iterator. – ADB Apr 19 '16 at 16:15
  • 1
    ...a map object *is an iterator*. – jonrsharpe Apr 19 '16 at 16:16
  • Not being pedantic! But would like to see this in the docs (map object == iterator). In the meantime this is also helpful http://stackoverflow.com/questions/231767/what-does-the-yield-keyword-do-in-python?rq=1 – ADB Apr 19 '16 at 16:26
  • 1. The docs tell you repeatedly that the result is an iterator, the fact that it's specifically a map object is largely irrelevant (not least because, told that you get an iterator and finding you get a map object, the fact that a map object is an iterator should have been your first conclusion). 2. Don't tell me, I don't maintain Python. – jonrsharpe Apr 19 '16 at 16:32
  • In case anyone (beside jonrsharpe) has the same question that I did, a good stackexchange question is here: http://stackoverflow.com/questions/231767/what-does-the-yield-keyword-do-in-python – ADB Apr 19 '16 at 20:51

1 Answers1

37

map returns a stateful iterator in Python 3. Stateful iterators may be only consumed once, after that it's exhausted and yields no values.

In your code snippet you consume iterator multiple times. list(m) each time tries to recreate list, and for second and next runs created list will always be empty (since source iterator was consumed in first list(m) operation).

Simply convert iterator to list once, and operate on said list afterwards.

m = map(lambda x: x**2, range(0,4))
l = list(m)
assert sum(l) == 14
assert sum(l) == 14
toriningen
  • 7,196
  • 3
  • 46
  • 68
Łukasz Rogalski
  • 22,092
  • 8
  • 59
  • 93
  • 1
    If you'd like to avoid this behavior with generators, you could instead skip the map and go straight to a list using a list comprehension. `m = [x**2 for x in range(4)]` – MS-DDOS Apr 07 '16 at 20:57
  • 1
    I have altered your answer, because `map()` returns iterators, not generators - `isinstance(map(lambda x: x**2, range(0,4)), types.GeneratorType) == False`. Returned object lacks `send()`, `throw()`, which is logical due to `map()` semantics, as it's not possible to communicate back to the object being mapped through. – toriningen Jan 15 '19 at 10:17