Hello everyone,
I'm currently in the process of self-study journey, so I have some questions arisen from time to time. Today I would like to talk about iterables and iterators,(ask for your help actually ^_^). Before I'll continue, just wanted to draw your attention to the fact that I did some RTFM before posting. Links: 1. map() built-in definitions: https://docs.python.org/3/library/functions.html#map - for Python 3.X https://docs.python.org/2.6/library/functions.html#map - for Python 2.6.X 2. Glossary definitions of "iterable " and "iterator": https://docs.python.org/3/glossary.html?highlight=glossary 3. Iterator Types: https://docs.python.org/2/library/stdtypes.html#typeiter 4. iter() definition: https://docs.python.org/2/library/functions.html#iter 5. Some StackOverflow links, related to the topic: http://stackoverflow.com/questions/13054057/confused-with-python-lists-are-t hey-or-are-they-not-iterators http://stackoverflow.com/questions/9884132/understanding-pythons-iterator-it erable-and-iteration-protocols-what-exact http://stackoverflow.com/questions/19523563/python-typeerror-int-object-is-n ot-iterable http://stackoverflow.com/questions/538346/iterating-over-a-string 6. And of course, re-read couple of times a relevant parts of the book ('Learning Python" by Mark Lutz). But the questions still persist, maybe because those examples look too esoteric though. Another warning: Despite all my attempts to make my questions as short as possible it still looks huge. My apologies. The problem: Here is the book's example: Consider the following clever alternative coding for this chapter's zip emulation examples, adapted from one in Python's manuals at the time I wrote these words: def myzip(*args): iters = map(iter, args) while iters: res = [next(i) for i in iters] yield tuple(res) Because this code uses iter and next, it works on any type of iterable. Note that there is no reason to catch the StopIteration raised by the next(it) inside the comprehension here when any one of the arguments' iterators is exhausted-allowing it to pass ends this generator function and has the same effect that a return statement would. The while iters: suffices to loop if at least one argument is passed, and avoids an infinite loop otherwise (the list comprehension would always return an empty list). This code works fine in Python 2.X as is: >>> list(myzip('abc', 'lmnop')) [('a', 'l'), ('b', 'm'), ('c', 'n')] But it falls into an infinite loop and fails in Python 3.X, because the 3.X map returns a one-shot iterable object instead of a list as in 2.X. In 3.X, as soon as we've run the list comprehension inside the loop once, iters will be exhausted but still True (and res will be []) forever. To make this work in 3.X, we need to use the list built-in function to create an object that can support multiple iterations: >>>def myzip(*args): iters = list(map(iter, args)) # Allow multiple scans ...rest as is... Run this on your own to trace its operation. The lesson here: wrapping map calls in list calls in 3.X is not just for display! *********END OF THE BOOK EXAMPLE************ According to the book , in order to get things work properly in Python 3.X, I should write this code: >>> def myzip(*args): iters = list(map(iter, args)) while iters: res = [next(i) for i in iters] yield tuple(res) And all seemed to be clear here, but, when I tried to run this thing: >>> k= myzip(1, 2, 3, 4) >>> next(k) I got this result: Traceback (most recent call last): File "<pyshell#73>", line 1, in <module> next(k) File "<pyshell#65>", line 2, in myzip iters = list(map(iter, args)) TypeError: 'int' object is not iterable Finding the problem: I started to investigate further in order to catch the bug: What I've tried? 1. >>> L = [1, 2, 3, 4] >>> iter(L) is L False ---> According to the "theory" it's OK, because list doesn't have self iterators. >>> k = iter(L) >>>print(k) <list_iterator object at 0x03233F90> >>> next(k) 1 >>> print(list(map(iter, L))) Traceback (most recent call last): File "<pyshell#88>", line 1, in <module> print(list(map(iter, L))) TypeError: 'int' object is not iterable 2. I went to strings: >>> S = 'spam' >>> iter(S) is S False ---> Same about strings >>> string = iter(S) >>> string <str_iterator object at 0x02E24F30> >>> next(string) 's' and so on. And then just tried this one: >>> print(list(map(iter, S))) [<str_iterator object at 0x02E24FF0>, <str_iterator object at 0x02E24CF0>, <str_iterator object at 0x02E24E10>, <str_iterator object at 0x02E24DF0>] -----> At this moment "Wrecking Ball" song played in my head %))))))) >>> k = list(map(iter,S)) >>> next(k[0]) 's' >>> next(k[0]) Traceback (most recent call last): File "<pyshell#99>", line 1, in <module> next(k[0]) StopIteration So I may admit that I found the problem, but can't explain it to myself. Finally I've checked the map() definition. According to python.org: "Return an iterator that applies function to every item of iterable, yielding the results." The questions: And as I've promised the question section: 1. What actually map() trying to do in Python 3.X? I mean, why is this works fine: >>>L = [1, 2, 3, 4] >>> k = iter(L) >>> next(k) 1 and so on. But not this: >>> list(map(iter, L)) Traceback (most recent call last): File "<pyshell#88>", line 1, in <module> print(list(map(iter, L))) TypeError: 'int' object is not iterable 2. Why strings are allowed "to become" an iterators(self-iterators)? Maybe because of files(reading from file) ? I mean why, is this possible: >>> print(list(map(iter, S))) [<str_iterator object at 0x02E24FF0>, <str_iterator object at 0x02E24CF0>, <str_iterator object at 0x02E24E10>, <str_iterator object at 0x02E24DF0>] What I'm trying to say, is that if I wouldn't tried to run the book's example with integer arguments(or tuples or lists as arguments) it wouldn't alarm this issue. And I've lived happily assuming that I understand iterables. )))) This is kind of a "dirty trick", because author do not talk about this anywhere. 3. The last question Author says: " But it falls into an infinite loop and fails in Python 3.X, because the 3.X map returns a one-shot iterable object instead of a list as in 2.X. In 3.X, as soon as we've run the list comprehension inside the loop once, iters will be exhausted but still True. To make this work in 3.X, we need to use the list built-in function to create an object that can support multiple iterations". (Like:"Wat?!" ^_^) Why the infinite loop would be there and why should list() to make it finite? o_0 Hope you have not get annoyed too much. Thanks in advance, Ivan
-- https://mail.python.org/mailman/listinfo/python-list