Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

python iterable list comprehension in list comprehension

I'm trying to figure out why my nested list comprehension doesn't work. Code:

def myzip(*args):
    try:
        x = [iter(i) for i in [*args]]
        while True:
        # for i in range(5):
            yield [next(ii) for ii in x]
            # I want to know why the commented line below won't work
            # yield [next(ii) for ii in [iter(i) for i in [*args]]]
            # or why this outputs [1, 'a']
            # return [next(ii) for ii in [iter(i) for i in [*args]]]
    except:
        pass


def main():
    print(list(myzip([1, 2, 3, 4, 5], ['a', 'b', 'c'])))

if __name__ == '__main__':
    main()

if I assign [iter(i) for i in [*args]] to x and then use this list comprehension [next(ii) for ii in x] everything works ok. and the output will be:

[[1, 'a'], [2, 'b'], [3, 'c']]

But if I try to ommit variable x and do it in one bigger list comprehension (commented line) [next(ii) for ii in [iter(i) for i in [*args]]] it goes in infinite loop. If you replace infinite loop with the for loop (commented line), the output is:

[[1, 'a'], [1, 'a'], [1, 'a'], [1, 'a'], [1, 'a']]

Moreover, if I try return [next(ii) for ii in [iter(i) for i in [*args]]] it just returns:

[1, 'a']

Can someone tell me why is that?

like image 446
user3613919 Avatar asked Nov 07 '22 05:11

user3613919


1 Answers

so i'm not going to answer your question directly (as the comments pretty well have this covered), but i am going to show how this can be done as a horrible one liner, just for fun:

def my_zip(*args):
    yield from iter(lambda data=[iter(a) for a in args]: [next(d) for d in data], None)

there are three parts.

  • the first usage of iter, after yield from, creates a callable_iterator. this will repeat calling its first argument (the lambda) until it either sees its second argument (the sentinel value, in this case None) OR it gets a StopIteration error it encounters any exception. (in this case, it will be a StopIteration exception.
  • in the lambda i have a default value for data which is a list of the iterators you want to iterate over. this gets created once, so each subsequent call to the function will reference this.
  • finally, in the body of the lambda, i manually advance each of the iterators in data one time per function call. this will continue until one of the iterators is exhausted, at which point a StopIteration will be raised. as you can see, in this case, the sentinel value doesn't matter because the first iter call will bail as soon as it gets its StopIteration this will be bubbled up to the list consumer which causes it to, well, stop iterating. because of the exception, the sentinel value doesn't matter.

finally: please never actually do this in real code.


edit: some sample output

In [90]: list(my_zip('abcd', [1,2,3]))
Out[90]: [['a', 1], ['b', 2], ['c', 3]]

In [91]: list(my_zip('abcd', [1,2,3,4,5,6]))
Out[91]: [['a', 1], ['b', 2], ['c', 3], ['d', 4]]

explanation edits: hat tip to @superb rain for their smart comments below.

like image 72
acushner Avatar answered Nov 15 '22 10:11

acushner