New teacher in town project under LEARN INTERMEDIATE PYTHON 3

You can call the __next__ method (almost always via next()) without ever calling the __iter__ method which means that you’d be trying to access a self.index that has yet to be defined. For an iterator where you must count over a sequence I think initialising some kind of sequence index would normally done in __init__ for the very reason you mention.

On that note the standard for iterators is to return themselves without changing their current position such that iter(iterator_object) is iterator_object. This is how the built-in iterator types I’m aware of all work and the iterator itself can be exhausted they don’t reset every time you call iter on them, you’d have to call iter on the original iterable instead or create a new iterator.

a = 1, 2, 3, 4, 5, 6, b = iter(a) print(iter(b) is b) c = zip(b, b) # The results of this are very different to an iterator that resets print(next(c)) print(next(c))

The example with zip here shows how iterators normally behave. I think sticking self.index = 0 in __iter__ here may have been a mistake.

2 Likes

I am so completely lost as to why when I try to add an index value to my iter I keep getting an error saying I need it in my init when I dont see examples of that in other forums. Because of this when I try to print next it doesnt move to the next item in the list because there is no way for me to iterate on the index.

class ClassroomOrganizer:
  def __init__(self):
    self.sorted_names = self._sort_alphabetically(student_roster)
    self.n = 0    
  def __iter__(self):
    return self
  
  def __next__(self):
    if self.n < len(student_roster):
      return self
    else:
      raise StopIteration
    self.n += 1

  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
  
my_class = ClassroomOrganizer()
for i in range(10):
  print(next(my_class))

At present your __next__ makes no use of a indexes or otherwise so there’s no route for you to actually use next to step through the elements of your sequence. You’ll want to add in some way of accessing different elements.

Overall this still seems like a really weird way to use an iterator, this sort of iteration is more common in node based structures. This looks like one of those cases where you’ve got a class trying to do too much. For my two cents I’d suggest dropping __next__ entirely and changing __iter__ to be:

def __iter__(self):
    return iter(self.sorted_names)

which is simpler, more readable and much better behaved. If you must have a customised iterator returned then consider an instance of a different iterator type, either a newly defined type or perhaps a generator.

1 Like

I think this is very specific to the way the lesson is trying to get people to think about iterators… They pretty much defined any iterator as something requiring an iter and a next

You’re quite right, the iterator does require them. But it’s often cleaner and easier to make a new type iterable than to make it an iterator itself (creation of iterators is where things like generators shine).

My problem is combining the heavy lifting of sorting and storing a new list with various associated methods like get_students_with_subject together with the iterator itself which seems like there’s too much going on for a single class. I’d kind of expect those things to be separate which is the norm for most of the built-in types too, e.g. iter(list) creates a low-memory list_iterator instance that simply accesses elements from an existing list (rather than necessitate the creation of new lists every time you wished to iterate).

Having to re-create your sorted sequence instance every time you wanted to iterate seems like repetition that could be avoided by following that pattern. Why break convention for the sake of breaking convention, especially if it actually winds up making things less flexible?

If you think iter(sorted_list) is disallowed by the requirements and you must do a customised iterator, in which a generator is also unacceptable then personally I’d suggest doing so as a separate class. Your sorted sequence and all its associated methods are one iterable class instance containing an __iter__ method which returns an instance of the separate iterator class (which then implements __iter__ and __next__). That seems like a route to both practice creating iterators whilst remaining flexible and following an established pattern.

Hopefully that was clear and something worthwhile keeping in mind :crossed_fingers:. At the end of the day it’s your project so it’s your call for what you actually implement :slightly_smiling_face:.

2 Likes

Hi bro, I was stucked for a good while as well … at first I didnt understand, but now I get it:

first of all, u need to understand what these two methods do:

Class ClassroomOrganizer:
  def __init__(self):
    self.sorted_names = self._sort_alphabetically(student_roster)

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

once u understand it , its pretty straightforward … the class constructor itself provides an object member sorted_names (which is a list) with all of the names sorted for you (the protected method _sort_alphabetically does this for u free of charge)

then, u only need to care for defining the iterator protocol for our ClassroomOrganizer, which is to define the iter and next method … this was my solution, and try code:

 def __iter__(self):
    self.c = 0
    return self
  
  def __next__(self):
    if self.c < len(student_roster):
      x = self.sorted_names[self.c]
      self.c += 1
      return x
    else:
      raise StopIteration


a = ClassroomOrganizer()
aIter = iter(a)
print(aIter)
for s in aIter:
  print(next(aIter))
2 Likes

This is what I’ve done, seems to work well

--------------------------

New Teacher in Town

#script.py

from roster import student_roster
from classroom_organizer import ClassroomOrganizer
import itertools

iter_student_roster = iter(student_roster)

for i in range(len(student_roster)):
print(next(iter_student_roster))

3

y = ClassroomOrganizer()
yIter = iter(y)
print(list(yIter))
for student in yIter:
print(student)

4

print(list(y.two_students_per_table()))

5

students_math = y.get_students_with_subject(“Math”)
students_science = y.get_students_with_subject(“Science”)
students_Math_Science = itertools.chain(students_math, students_science)
students_Math_Science_tables = itertools.combinations(students_Math_Science, 4)
print(list(students_Math_Science_tables))

#-----------

#classroom_organizer.py

Import modules above this line

from roster import student_roster
import itertools

class ClassroomOrganizer:
def init(self):
self.sorted_names = self._sort_alphabetically(student_roster)

def iter(self):
self.count = 0
return self

def next(self):
self.count += 1
if self.count >= len(student_roster):
raise StopIteration
else:
x = self.sorted_names[self.count]
return x

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

4

def two_students_per_table(self):
y = itertools.combinations(self.sorted_names, 2)
return y

#-----------

5 Likes

You’re 100% right but I imagine most people tackling this project just had their first exposure to the concept of implementing custom iterables in the form of the lessons just before it, which don’t explain any of the techniques you’re talking about here and in fact teach the concept using only examples of the bad/weird practices you’re recommending against :slight_smile:

The ClassroomOrganizer class is provided halfway-implemented by the project, and they explicitly tell you to make it iterable. I agree that simply passing the iter of the underlying list is the cleanest way to handle this, although it’s not even hinted at in the instructions that you should or even could do that. But yea, the reason you’re seeing this kind of wacky stuff is that it’s baked into the project itself.

The Intermediate Python course quality has been pretty good before this part, but the iterables section is kind of a hot mess. This project should be reworked along with the iterables lesson preceding it, IMO.

6 Likes

useful thread, thx for your answers

2 Likes

I just have to say this Project gives me serious brain wrinkles. I haven’t experienced any less frustrating task since that wacky linear regression project out of nowhere all in the beginning of the Python 3 beginners lessons. This one is freaking clear as mud. Trying to read through this forum thread too, without actually understanding much more, nor find a solution. The lack of a View solution, or a run through video to explain wtf going on here drags this course a bit down imo. Tempted to just skip the whole lesson and forget all about it lol.

5 Likes

Thanks for your question. The directions at this part are badly written and it’s a bit frustrating to spend time decoding what they wanted to say.

2 Likes

in task 3 it print just 9 students?

I am having trouble with finding the error here. Could the error also be in the ClassroomOrganizer code?

import itertools from classroom_organizer import ClassroomOrganizer from roster import student_roster class student_roster(): index = 0 def __iter__(self): self.student_roster = student_roster for i in range(10): return student_roster my_class = ClassroomOrganizer() my_classIter = iter(my_class) print(list(my_classIter)) for student in my_classIter: print(student)
import itertools from roster import student_roster # Import modules above this line class ClassroomOrganizer: def __init__(self): self.sorted_names = self._sort_alphabetically(student_roster) for students in student_roster: return self.sorted_names def __iter__(self): self.count = 0 return self def __next__(self): if self.count < len(student_roster): x = self.sorted_names[self.count] self.count += 1 return x else: raise StopIteration 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 def final_roster_combinations(self, selected_students): final_roster_combinations = list(itertools.combinations(self.selected_students, 2)) return final_roster_combinations

You are very close. All you need to do to make this work is put self.count in init() method and define it as zero and remove it from iter(). Defining Iter in the class will overwrite what iter() does when called on this class the same as defining next(). Once you define both of these in the class then you can simply call next() commands and for loops on the instance object as if it were an iterable itself. I’ll provide my code snippet.

import itertools from roster import student_roster # Import modules above this line class ClassroomOrganizer: def __init__(self): self.sorted_names = self._sort_alphabetically(student_roster) self.counter = 0 def __iter__(self): return self def __next__(self): if self.counter < len(student_roster): nxt_value = self.sorted_names[self.counter] self.counter += 1 return nxt_value else: raise StopIteration 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 classroom = ClassroomOrganizer() for student in classroom: print(student)

Thank you for your attention to this. Your response was helpful in my moving on and understanding what was incorrect.

1 Like

No, @javierortizmir599394 has provided a solution above

1 Like

I get an IndexError in my code, despite self.count not becoming larger the length of the list:

student_roster = [ { "name": "Karina M", "age": 8, "height": 48, "favorite_subject": "Math", "favorite_animal": "Dog" }, { "name": "Yori K", "age": 7, "height": 50, "favorite_subject": "Art", "favorite_animal": "Cat" }, { "name": "Alex C", "age": 7, "height": 47, "favorite_subject": "Science", "favorite_animal": "Cow" }, { "name": "Esmeralda R", "age": 8, "height": 52, "favorite_subject": "History", "favorite_animal": "Rabbit" }, { "name": "Sandy P", "age": 7, "height": 49, "favorite_subject": "Recess", "favorite_animal": "Guinea Pig" }, { "name": "Matthew Q", "age": 7, "height": 46, "favorite_subject": "Music", "favorite_animal": "Walrus" }, { "name": "Trudy B", "age": 8, "height": 45, "favorite_subject": "Science", "favorite_animal": "Ladybug" }, { "name": "Benny D", "age": 7, "height": 51, "favorite_subject": "Math", "favorite_animal": "Ant" }, { "name": "Helena L", "age": 7, "height": 53, "favorite_subject": "Art", "favorite_animal": "Butterfly" }, { "name": "Marisol R", "age": 8, "height": 50, "favorite_subject": "Math", "favorite_animal": "Lion" } ] import itertools # Import modules above this line class ClassroomOrganizer: def __init__(self): self.sorted_names = self._sort_alphabetically(student_roster) 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 #MY QUESTION COVERS THE CODE BELOW def __iter__(self): self.count = 0 return self def __next__(self): if self.count < len(self.sorted_names): self.count += 1 return self.sorted_names[self.count] else: raise StopIteration #def seating_plan(self): register = ClassroomOrganizer() register_iterable = iter(register) for item in register_iterable: print(next(register_iterable))

I think the error is because you are increasing the index by 1, before using it.
You can avoid using the problem of using self.count as the index and having to increase self.count at the same time
by making another variable.

  def __next__(self):
    if self.count < len(self.sorted_names):
      index = self.count
      self.count += 1
      return self.sorted_names[index]
    else:
      raise StopIteration

Notice that self.count could be larger than (or equal to) the length of the list because you had
self.count += 1
after checking if self.count < len(self.sorted_names)

alternative

An alternative is to use some math to get the appropriate index: self.count - 1

  def __next__(self):
    if self.count < len(self.sorted_names):
      self.count += 1
      return self.sorted_names[self.count - 1]
    else:
      raise StopIteration
1 Like

Another simple alternative:

if self.count + 1 < len(self.sorted_names):
1 Like

@janbazant1107978602 , @mtf Thanks guys :slight_smile:

1 Like