Python: Why is this program, that prints the numbers 1 to 10 using a while loop, not working?

Note the use of iter.  range(1, 11) is not an iterator, but an iterable, meaning an object that can produce an iterator. This is a very subtle but important distinction. A list is an iterable, but not an iterator. An iterator holds state, meaning that it remembers which value to produce next. You can't do that with a list, but you can turn a list into an iterator.
For instance:
>>> my_list = [1, 2, 3, 4, 5]>>> my_list_iterator = iter(my_list)>>> first_value = next(my_list_iterator)>>>>>> first_value1>>> second_value = next(my_list_iterator)>>> second_value2>>> type(my_list)<class 'list'>>>> type(my_list_iterator)<class 'list_iterator'>
If you want to know how it works 'under the hood', let's look at the following examples:
>>> class Counter:…     def __init__(self, start=1):…         self.current = start…     def __next__(self):…         value = self.current…         self.current += 1…         return value…     def __iter__(self):…         return self…>>> my_counter = Counter()>>> next(my_counter)1>>> next(my_counter)2>>> next(my_counter)3
my_counter is an iterator, since it holds state (it remembers where it is, so to speak). Specifically, it has a __next__() method defined. It can produce the next value at any point. It also is an iterable, since it has the __iter__() method defined.
A generator is an iterator that lazily produces values, for instance:
def counter(current=1):    while True:        yield current        current += 1
And yes, generators thus have state:
>>> my_counter = counter()>>> next(my_counter)1>>> next(my_counter)2>>> next(my_counter)3
What is interesting now, is that range is not a generator, contrary to popular belief. range is an iterable, but not an iterator, and thus definitely not a generator. This might seem annoying, but there are some good reasons for this (extra attributes such as start, stop and step, immutability (can't be changed; for something to hold state, it needs toe be mutable)). It is of course possible to implement range as a generator:
>>> def my_range_generator(start, stop, step=1):…     current = start…     while current < stop:…         yield current…         current += step…>>> test_range = my_range_generator(1, 11, 2)>>> list(test_range)[1, 3, 5, 7, 9]
I hope I've given enough background to start understanding your problem, rather than merely correcting it.

Answer by Ben Baert:

Your question shows a lack of understanding from what is going on, so I'm going to take the time to talk about this all in detail.
In the first line, you write

for loop in range(1, 11).

The name

loop

does not make sense at all. The whole construction is one loop, i.e. you loop only once through the numbers from 1 to 10. What you wrote makes it seem as if you loop 10 times, which is not true. So while your code is correct, the name of the variable indicates a lack of understanding of what is going on.

for number in range(1, 11):
    print(number)
That whole thing (those two full lines of code) is the

for loop

. One

for loop

.

I don't understand what you want to achieve with the

while

loop for a number of reasons. Firstly, if you want to iterate through all the numbers and give the user the option to break out of the loop after each number, you can simply do that with a for-loop. 

for number in range(1, 11):
   print(number)
   user_input = input("Do you want to break? (Y/N)")
   if user_input.lower().startswith('y'): #allow for variations and upper/lower case
        break
Your can fix the printing of

range(1, 11)

by wrapping it in

list()

, but that still doesn't seem to be what you want. I assume you want to do the same thing as in the for loop, namely printing each number, but giving an option to break out of the loop after each number. Why would you have a loop otherwise, to print the same thing over and over again?

If you don't want to use a for loop, you can use something like:
current = 1
while True:
  print(current)
  current += 1
  user_input = input("Do you want to break? (Y/N)")
  if user_input.lower().startswith('y') or current == 10:
    break
or
numbers = iter(range(1, 11))
while True:
    current = next(numbers)
    print(current)
    user_input = input("Do you want to break? (Y/N)")
    if user_input.lower().startswith('y') or current == 10: #avoid StopIteration
        break
Note the use of

iter

range(1, 11)

is not an iterator, but an iterable, meaning an object that can produce an iterator. This is a very subtle but important distinction. A

list

is an iterable, but not an iterator. An iterator holds state, meaning that it remembers which value to produce next. You can't do that with a list, but you can turn a list into an iterator.

For instance:
>>> my_list = [1, 2, 3, 4, 5]
>>> my_list_iterator = iter(my_list)
>>> first_value = next(my_list_iterator)
>>>
>>> first_value
1
>>> second_value = next(my_list_iterator)
>>> second_value
2
>>> type(my_list)
<class 'list'>
>>> type(my_list_iterator)
<class 'list_iterator'>
If you want to know how it works 'under the hood', let's look at the following examples:
>>> class Counter:
...     def __init__(self, start=1):
...         self.current = start
...     def __next__(self):
...         value = self.current
...         self.current += 1
...         return value
...     def __iter__(self):
...         return self
...
>>> my_counter = Counter()
>>> next(my_counter)
1
>>> next(my_counter)
2
>>> next(my_counter)
3
my_counter

is an iterator, since it holds state (it remembers where it is, so to speak). Specifically, it has a

__next__()

method defined. It can produce the next value at any point. It also is an iterable, since it has the

__iter__(
)

method defined.

A generator is an iterator that lazily produces values, for instance:
def counter(current=1):
    while True:
        yield current
        current += 1
And yes, generators thus have state:
>>> my_counter = counter()
>>> next(my_counter)
1
>>> next(my_counter)
2
>>> next(my_counter)
3
What is interesting now, is that

range

is not a generator, contrary to popular belief.

range

is an iterable, but not an iterator, and thus definitely not a generator. This might seem annoying, but there are some good reasons for this (extra attributes such as

start

,

stop

and

step

, immutability (can't be changed; for something to hold state, it needs toe be mutable)). It is of course possible to implement

range

as a generator:

>>> def my_range_generator(start, stop, step=1):
...     current = start
...     while current < stop:
...         yield current
...         current += step
...
>>> test_range = my_range_generator(1, 11, 2)
>>> list(test_range)
[1, 3, 5, 7, 9]
I hope I've given enough background to start understanding your problem, rather than merely correcting it.

Python: Why is this program, that prints the numbers 1 to 10 using a while loop, not working?

Advertisements

Leave a comment

Filed under Life

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s