0

(I am quite a newbie in Python, so lots of things puzzle me even after reading the tutorial...)

Initially, I had the code like the following:

strings = ['>abc', 'qwertyu', '>def', 'zxcvbnm']
matrix  = zip(*strings)
for member in matrix:
    print("".join(member)) # characters are printed as expected

-- which did what I expected. But then for some reason I wanted to determine the number of members in matrix; as len(matrix) gave an error, I decided to copy it with converting to the list: mtxlist = list(matrix). Surprisingly, after this line the content of matrix seems to be changed - or at least I cannot use it the same way as above:

strings = ['>abc', 'qwertyu', '>def', 'zxcvbnm']
matrix  = zip(*strings)
mtxlist = list(matrix) # this assignment empties (?) the matrix
for member in matrix:
    print("".join(member)) # nothing printed

Can anybody explain what is going on there?

Vasily A
  • 8,256
  • 10
  • 42
  • 76

1 Answers1

8

You're using Python 3, correct?

zip returns a generator that can only be iterated once. If you want to use it more than once, then your options are:

Write zip(*strings) each time you need it.

matrix = tuple(zip(*strings))

(iterate matrix as many times as you like. This is the easy option. The downside is that if zip(*strings) is big then it uses a lot of memory that the generator doesn't.)

matrix1, matrix2 = itertools.tee(zip(*strings))

(iterate each of matrix1 and matrix2 once. This is worse than the tuple in your usage, but it's useful if you want to partially consume matrix1, then use some of matrix2, more of matrix1, etc)

def matrix():
    return zip(*strings)
# or
matrix = lambda: zip(*strings)

(iterate but using matrix(), not matrix, as many times as you like. Doesn't use extra memory for a copy of the result like the tuple solution, but the syntax for using it is a bit annoying)

class ReusableIterable:
    def __init__(self, func):
        self.func = func
    def __iter__(self):
        return iter(self.func())

matrix = ReusableIterable(lambda: zip(*strings))

(iterate using matrix as many times as you like. Deals with the syntax annoyance, although you still have to beware that if you modify strings between iterations over matrix then you'll get different results.)

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
  • now I start to understand, thanks a lot! (yes, I am using Python 3.3). Unfortunately I couldn't find much details about such dynamic behavior in standard library at docs.python.org - probably there's some better source to read... Now I see that my title is not really correct as this case has nothing to do with `list()` itself – Vasily A Nov 23 '13 at 09:36
  • @VasilyA: I think you're OK. It kind of does have to do with `list`, and `list` is the case you encountered it with. But *any* callable, including `list`, that iterates over something will modify that thing if the thing is a one-use generator. – Steve Jessop Nov 23 '13 at 09:38
  • As for documentation: http://docs.python.org/3/library/functions.html#zip says that it returns an "iterator". http://stackoverflow.com/questions/9884132/ explains the difference between an *iterator* and an *iterable*. So the documentation tells you what you need to know, but only if you understand the jargon. http://docs.python.org/3/library/stdtypes.html#iterator-types documents the jargon, but perhaps doesn't make the difference as clear as it might be. There's probably more on the subject in the official tutorial. – Steve Jessop Nov 23 '13 at 09:42
  • great, with these additional links things are much more clear. Thanks! – Vasily A Nov 23 '13 at 09:53