1
\$\begingroup\$

I am a relatively new programmer. I am trying to use iter and cycle to cycle over a list (key) and 'pair' each item with each consecutive item in the second list (message). The code below works as expected, producing the desired pairs and stopping when it is done. It does not, however, execute that last print statement ('Okay, it worked!') because it hits 'StopIteration' instead.

I can see some kludgy ways to get around this, but how can I more neatly "handle" the StopIteration and have the code "continue"?


key = [3, 1, 20]
message = ['a', 'b', 'c', 'd', 'e', 'f']

print(key)
print(message)
print('\n')

# Try making a for loop that cycles through the key and iterates
# over the message
m = iter(message)
for k in cycle(key):
    print(f'{k}:{next(m)}')
print('Okay, it worked!')
```
\$\endgroup\$
1
  • 2
    \$\begingroup\$ Please include any indispensable imports. Show the kludgy code - with non-working code (only), your question is off topic. When editing the post, anyway, add a newline at the end of a trailing ~~~ (or ```) line. \$\endgroup\$
    – greybeard
    May 5, 2020 at 4:04

1 Answer 1

6
\$\begingroup\$

Creating a "manual" iterator using iter and then calling next can be tricky, as you found out. Once an iterator is exhausted, it raises a StopIteration to signal its end. If you do not catch that in a try/except block, your program will exit ungracefully.

Luckily, message, being a list, is perfectly iterable without being explicit about it. Simply using it in a for loop will do the iter/next magic for you, and the loop will exit once StopIteration is reached, without error.

zip is a built-in so you can iterate over multiple iterables at once. zip will iterate until the shortest of the passed iterables is exhausted. This can be tricky if you expect the iterables to be of the same length. There is currently talk about a strict flag in the Python ideas mailing list, which would throw errors if strict=True and the iterables are of different lengths.

Assuming you meant to include from itertools import cycle, alongside cycle, of course message is exhausted first. cycle being infinite, this suprises no one in this case. The for loop then simply ends after the last message element.

This hopefully does what you want to do:

from itertools import cycle

keys = [3, 1, 20]
message = ["a", "b", "c", "d", "e", "f"]

print(keys, message, sep="\n", end="\n" * 2)

for key, letter in zip(cycle(keys), message):
    print(f"{key}: {letter}")

print("Okay, it worked!")

This is more like your version, needlessly verbose. Never do this in reality! However, it can help you understand what is going on in your case:

from itertools import cycle

keys = [3, 1, 20]
message = ["a", "b", "c", "d", "e", "f"]

print(keys, message, sep="\n", end="\n" * 2)

message_iter = iter(message)

for key in cycle(keys):
    try:
        print(f"{key}: {next(message_iter)}")
    except StopIteration:
        break  # Break out of infinite loop

print("Okay, it worked!")
\$\endgroup\$
1
  • 1
    \$\begingroup\$ This code solves my problem, and gives me some perspective on iter, thanks. I had tried zip first actually, but run into the problem of the shorter list "running out". This solves that. \$\endgroup\$ May 4, 2020 at 18:24

Not the answer you're looking for? Browse other questions tagged or ask your own question.