Question on Advanced Python 3 Project, New Teacher in Town

New Teacher in Town | Codecademy

Hey, when I run this code, I receive an AttributeError: ‘ClassroomOrganizer’ object has no attribute ‘index’

I really dont get why as I am initializing the ‘index’ attribute in the iter_ method. What is the issue here?
Thanks,
Elissn

import itertools
# Import modules above this line
class ClassroomOrganizer:
  def __init__(self):
    self.sorted_names = self._sort_alphabetically(student_roster)

  def __iter__(self):
      self.index = 0
      return self

  def __next__(self):
      message = "{}".format(self.sorted_names[self.index])
      self.index += 1
      return message

  def _sort_alphabetically(self,students):
    names = []
    for student_info in students:
      name = student_info['name']
      names.append(name)
    return sorted(names)

  def get_students_with_subject(self, subject):
    selected_students = []
    for student in student_roster:
      if student['favorite_subject'] == subject:
        selected_students.append((student['name'], subject))
    return selected_students

c = ClassroomOrganizer()

print(next(c))
type or paste code here
1 Like

Two different issues here.

I’ll start with the conceptual one first:

What are iterators? They are things that iterate. What does it mean to iterate? Iterate means “to repeat” or “do again”.

So when we overload a class dundermethod __iter__ we are saying, if i ask for the iterator for this class, please do this instead of your default behavior. For an example of that, just think about how lists or sets or dictionaries just work by default in a for-loop (any iterable collection). This means when you invoke a for loop with that collection, it uses that default behavior as you would expect it to.

But when we have a user-defined class, the python interpreter doesn’t know what to do if you just throw a for-loop at it. But if we overload the dunder methods, it becomes possible to now use it as we would any common iterable object.

So for example, if you do this correctly, at the end you should be able to write

c = ClassroomOrganizer()
for student in c:
  print(student)

How is this useful in actual programming? Besides being a good learning experience, I have previously worked with self defined tree classes (python doesn’t have a tree class). Sometimes it’s nice to just be able to traverse the tree in some sort of order, and this operation is so common, it’s reasonable to just hook it up so that a for-loop can do some tree traversal by default (I think this is really neat).

Ok as to your error, one is hidden and one is explicit (the one you wrote about).

Since you are treating index as a data member of your class, you have to define it first for your class. Seems simple but could be easy to miss. In you constructor method (__init__), you never define self.index, so this is why the error is thrown. You should set it to a sensible default (and no, setting it to 0 in the iter method doesn’t count as initialization, although setting it to 0 is a good default, and that line shouldn’t change).

The implicit error here is that your __next__ function is unbound and will most definitely throw some sort of segfault/index error. The reason is that you will try to index something in a memory location that’s not defined. (For example, consider what happens if you call next and the index goes out of bounds?)

Python has an exception made just for that case raise StopIteration. So if you see that you have exhausted the array, instead of doing your normal next behavior you should raise that exception.

This way, you can use your self-defined class in a for-loop. Neat!

2 Likes

Thank you @toastedpitabread for taking a look at the question and commenting on the code sample, as well as shed more light on iterators and overloading. I’ve never really been clear on this project and cobbled together code that gave me the correct output but was never sure if the iterator was running, or if the built-in was.

Here is my reworked ClassroomOrganizer class definition (in ‘classroom_organizer’ module) that gets me to step 3:

class ClassroomOrganizer:
  def __init__(self):
    self.sorted_names = self._sort_alphabetically(student_roster)
    self.index = -1
  def __iter__(self):
    return self
  def __next__(self):
    self.index += 1
    return self[self.index]

  def roll_call(self):
    r = iter(self.sorted_names)  # overload or built-in?
    while True:
      try:
        print (next(r))
      except StopIteration:
        break

script.py

s = ClassroomOrganizer()
s.rollcall()

Output

Alex C
Benny D
Esmeralda R
Helena L
Karina M
Marisol R
Matthew Q
Sandy P
Trudy B
Yori K