FAQ: Iterables and Iterators - Custom Iterators II

This community-built FAQ covers the “Custom Iterators II” exercise from the lesson “Iterables and Iterators”.

Paths and Courses
This exercise can be found in the following Codecademy content:

FAQs on the exercise Custom Iterators II

There are currently no frequently asked questions associated with this exercise – that’s where you come in! You can contribute to this section by offering your own questions, answers, or clarifications on this exercise. Ask or answer a question by clicking reply (reply) below.

If you’ve had an “aha” moment about the concepts, formatting, syntax, or anything else with this exercise, consider sharing those insights! Teaching others and answering their questions is one of the best ways to learn and stay sharp.

Join the Discussion. Help a fellow learner on their journey.

Ask or answer a question about this exercise by clicking reply (reply) below!
You can also find further discussion and get answers to your questions over in #get-help.

Agree with a comment or answer? Like (like) to up-vote the contribution!

Need broader help or resources? Head to #get-help and #community:tips-and-resources. If you are wanting feedback or inspiration for a project, check out #project.

Looking for motivation to keep learning? Join our wider discussions in #community

Learn more about how to use this guide.

Found a bug? Report it online, or post in #community:Codecademy-Bug-Reporting

Have a question about your account or billing? Reach out to our customer support team!

None of the above? Find out where to ask other questions here!

I’m stuck in an infinite loop! Here is my code:

class CustomerCounter:
  # Write your code below:
  def __iter__(self):
    self.count = 0
    return self

  def __next__(self):
    self.count += 1
    return self.count
    if self.count > 100:
      raise StopIteration

customer_counter = CustomerCounter()
for customer in customer_counter:
  print(customer)

If I ran this code it takes too long to run; it’s probably stuck in an infinite loop. I decided to comment the iteration part like this:

class CustomerCounter:
  # Write your code below:
  def __iter__(self):
    self.count = 0
    return self

  def __next__(self):
    self.count += 1
    return self.count
    if self.count > 100:
      raise StopIteration

customer_counter = CustomerCounter()
# I commented the 2 next lines because there is an infinite loop:
#for customer in customer_counter:
  #print(customer)

Hi! I guess that’s because Python ignores everything that comes after the return statement within a function!

What I wanted to mention is: it would be cool (sorry if that actually happens at a later point) to point out that regular (non-custom) iterator objects don’t seem to count anything but rather “eat themselves”? I mean, that is interesting and kinda unexpected behavior, right?

2 Likes

They have already implemented the __iter__ and __next__ methods. If you don’t want to implement that, use generator functions which will be taught after in this topic. Anyway I’m going to not change it(maybe) to stop the infinite loop because I’ve already finished this course and I’m up to the Learn Sorting Algorithms with Python course.

I don’t understand why “for customer_count in customer_counter” works when the count is set at 0.

When iterating through something like a list, there’s a set amount of data to work with and you know which one is going to be used first, next etc. But where is this when there’s only a value of 0 being assigned to a class member instead of a collection.

You might already be aware but a for loop effectively calls the iter() function on the target of the loop.

So a for loop is basically equivalent to a while loop of the following style-

it = iter(customer_counter)
while True:
    try:
        customer_count = next(it)
        # execute statements in body of loop...
    except StopIteration:
        break

So before the loop ever begins iteration the iter function is called (which makes use of the __iter__ dunder method, setting the instance variable .count equal to zero). After that hopefully it’s clear how next simply increments this instance variable and returns the value?


Minor rant:
Personally I think the example used in this lesson breaks Python’s iterator protocol. If CustomerCounter itself is supposed to be an iterator then calling iter with this iterator object should return the exact same iterator object (it should not have side effects of resetting the iterator!).

This style can do some very alarming things with what should be an exhausted iterator (the protocol requires that once StopIteration is met in an iterator it only ever returns StopIteration). See-
https://docs.python.org/3/glossary.html#term-iterator.
https://docs.python.org/3/library/stdtypes.html#iterator-types

Once an iterator’s __next__() method raises StopIteration , it must continue to do so on subsequent calls. Implementations that do not obey this property are deemed broken.

The following displays the broken behaviour-

counter = CutomerCounter()
for value in counter: print(value, end=', ')  # Out: 1, 2, 3, 4, 5, 6, ...
# this iterator should be exhausted!
counter2 = iter(counter)
# note that either counter or counter2 can be used as they are the same object
for value in counter: print(value, end=', ')  # Out: 1, 2, 3, 4, 5, 6, ...

If CustomerCounter itself is an iterator then I believe it should be written like-

class CustomerCounter:

  def __init__(self):
    self.count = 0
    
  def __iter__(self):
    return self
  
  def __next__(self):
    if self.count < 100:
      self.count += 1
      return self.count
    else:
      raise StopIteration

This at least has the behaviour of a typical iterator.

Fortunately writing your own iterators is extremely uncommon since you can make use of either iterators for existing containers or if you really need customisation generators are absolutely perfect for this task.

2 Likes

Thank you! That actually makes a lot of sense. I guess it didn’t click that the value was only being initialized within the iteration protocol. Also thanks for the extra info, if i’m understanding this right, when instantiating an instance of the customer counter class once StopIteration has been reached it’ll never be able to iterate again.

1 Like

Since the CustomerCounter type is used as an iterator here I think that is entirely correct. There’s nothing stopping further new instances being created; you just shouldn’t reset an existing iterator object.

1 Like

Agreed! I’d love to see it revised to used best practices. I’d think at a minimum, you’d want two separate types, Thing and ThingInterator. I don’t see how an object can be its own iterator without running into problems like this… although maybe it’s possible if implemented differently?