How to iterate over all the instances of a class?

Question:

I know this has been asked before, but I couldn’t find an answer for my problem and all of the answers got me more confused.

So I created a class called Books, which takes as input a file location and the name of the file, and has a method called countwords which counts the words of the file.

I created a bunch of instances(are they called like this?) of this class, but I want to iterate the method CountWords on all of them. This is my code:

class Books():
    def __init__(self, path, fullname):
        self.path     = path
        self.fullname = fullname

    def count_words(self):
        try:
            with open(self.path + self.fullname, encoding='utf-8') as f:
                contents = f.read()
        except FileNotFoundError:
            print(f"Sorry, the file {self.fullname} doesn't exist.")
        else:
            words     = contents.split()
            num_words = len(words)
            print(f"The file {self.fullname} has about {num_words}" 
                  f"words.")             

alice        = Books("text_files/", 'alice.txt')
siddharta    = Books("text_files/", 'siddhartha.txt')
mobydick     = Books('text_files/', 'moby_dick.txt')
little_women = Books('text_files/', 'little_women.txt')

I want to write something like this:

for book in Books.list():
    book.count_words()

and get:

 The file alice.txt has about 29465 words. 
 The file siddhartha.txt has about 1500 words.
 The file moby_dick.txt has about 215830 words. 
 The file little_women.txt has about 189079 words. 

Printing the words count of all the instances associated with the Books() class, how can I achieve this? thank you!

EDIT:
I have tried different approaches but this is the one I’m using now:

  1. first one is importing weakref(don’t know what this is for).
  2. Then adding an empty list at the beginning of the class.
  3. after that use: self.__class__LISTNAME_append(weakref.proxy(self)) at the end of the def init
  4. Then you can loop through the LISTNAME.

code:

# 1 
import weakref

class Book():
  # 2
  book_list = []
  def __init__(self, path, fullname):
      self.path = path
      self.fullname = fullname
      # 3
      self.__class__.book_list.append(weakref.proxy(self))

  def count_words(self):
      try:
          with open(self.path + self.fullname, encoding='utf-8') as f:
              contents = f.read()
      except FileNotFoundError:
          print(f"Sorry, the file {self.fullname} doesn't exist.")
      else:
          words = contents.split()
          num_words = len(words)
          print(f"The file {self.fullname} has about {num_words} "
                f"words. ")

alice = Book("text_files/", 'alice.txt')

siddharta = Book("text_files/", 'siddhartha.txt')

mobydick = Book('text_files/', 'moby_dick.txt')

little_women = Book('text_files/', 'little_women.txt')

# 4
for book in Book.book_list:
  book.count_words()

Asked By: Alejandro Rosales

||

Answers:

Rather than having your class keep references to all the instances that have been created, I suggest you define your data first, and then create a collection of instances from that data.

For example:

book_paths = [('text_files/', 'alice.txt'),
              ('text_files/', 'siddharta.txt'),
              ('text_files/', 'moby_dick.txt'),
              ('text_files/', 'little_women.txt')]

books = []

for path, name in book_paths:
    book = Books(path, name)
    books.append(book)
    book.count_words()

You can also later iterate over books to do whatever you want. Another way to do this, using a list comprehension:

books = [Books(path, name) for path, name in book_paths]

for book in books:
    book.count_words()
Answered By: gmds

All you have to do is create a class variable which will store the book information when the instance is instantiated.

And then expose a class level method which return this list when Books.list gets called.

Here is the code for the same

class Books(object):
    _books_list = list()
    def __init__(self, path, fullname):
        self.path = path
        self.fullname = fullname
        self._books_list.append(self.path + self.fullname)

    def count_words(self):
        try:
            with open(self.path + self.fullname, encoding='utf-8') as f:
                contents = f.read()
        except FileNotFoundError:
            print(f"Sorry, the file {self.fullname} doesn't exist.")
        else:
            words = contents.split()
            num_words = len(words)
            print(f"The file {self.fullname} has about {num_words}"
                  f"words.")
    @classmethod
    def list():
        return self._books_list
alice = Books("text_files/", 'alice.txt')
siddharta = Books("text_files/", 'siddhartha.txt')
mobydick = Books('text_files/', 'moby_dick.txt')
little_women = Books('text_files/', 'little_women.txt')
Answered By: Optimus

that’s the wrong way to approach the problem.

I suggest you create a dictionary of classes instead, with filename as key:

instead of:

alice = Books("text_files/", 'alice.txt')
siddharta = Books("text_files/", 'siddhartha.txt')
mobydick = Books('text_files/', 'moby_dick.txt')
little_women = Books('text_files/', 'little_women.txt')

do:

books_dict = {name:Books('text_files/',name+".txt") for name in ["alice","siddhartha","moby_dick","little_women"}

Now you have a dictionary of Books instances. You can now iterate on it like this:

for book_name,book_instance in books_dict.items():
    print(book_name,book_instance)

If you want to “unregister” an instance, just do:

books_dict.pop("alice")

Better not create x variables for x instances. Use a federating structure.

Judging by your variable, it might be easier to comprehend by naming the class Book:

class Book():
    def __init__(self, path, fullname):
        self.path = path
        self.fullname = fullname

    def count_words(self):
        try:
            with open(self.path + self.fullname, encoding='utf-8') as f:
                contents = f.read()
        except FileNotFoundError:
            print(f"Sorry, the file {self.fullname} doesn't exist.")
        else:
            words = contents.split()
            num_words = len(words)
            print(f"The file {self.fullname} has about {num_words}" 
                  f"words.")    

make the instances of books as you’ve described

alice = Book("text_files/", 'alice.txt')
siddharta = Book("text_files/", 'siddhartha.txt')
mobydick = Book('text_files/', 'moby_dick.txt')
little_women = Book('text_files/', 'little_women.txt')

and add them into a list referenced as books:

books = [alice, siddhartha, mobydick, little_women]

then you can iterate over the list calling the count_words method on each book

Answered By: Ethan Fung
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.