Can I iterate over a class in Python?

Question:

I have a class that keeps track of its instances in a class variable, something like this:

class Foo:
    by_id = {}

    def __init__(self, id):
        self.id = id
        self.by_id[id] = self

What I’d like to be able to do is iterate over the existing instances of the class. I can do this with:

for foo in Foo.by_id.values():
    foo.do_something()

but it would look neater like this:

for foo in Foo:
    foo.do_something()

is this possible? I tried defining a classmethod __iter__, but that didn’t work.

Asked By: xorsyst

||

Answers:

If you want to iterate over the class, you have to define a metaclass which supports iteration.

x.py:

class it(type):
    def __iter__(self):
        # Wanna iterate over a class? Then ask that class for iterator.
        return self.classiter()

class Foo:
    __metaclass__ = it # We need that meta class...
    by_id = {} # Store the stuff here...

    def __init__(self, id): # new isntance of class
        self.id = id # do we need that?
        self.by_id[id] = self # register istance

    @classmethod
    def classiter(cls): # iterate over class by giving all instances which have been instantiated
        return iter(cls.by_id.values())

if __name__ == '__main__':
    a = Foo(123)
    print list(Foo)
    del a
    print list(Foo)

As you can see in the end, deleting an instance will not have any effect on the object itself, because it stays in the by_id dict. You can cope with that using weakrefs when you

import weakref

and then do

by_id = weakref.WeakValueDictionary()

. This way the values will only kept as long as there is a “strong” reference keeping it, such as a in this case. After del a, there are only weak references pointing to the object, so they can be gc’ed.

Due to the warning concerning WeakValueDictionary()s, I suggest to use the following:

[...]
    self.by_id[id] = weakref.ref(self)
[...]
@classmethod
def classiter(cls):
    # return all class instances which are still alive according to their weakref pointing to them
    return (i for i in (i() for i in cls.by_id.values()) if i is not None)

Looks a bit complicated, but makes sure that you get the objects and not a weakref object.

Answered By: glglgl

Magic methods are always looked up on the class, so adding __iter__ to the class won’t make it iterable. However the class is an instance of its metaclass, so the metaclass is the correct place to define the __iter__ method.

class FooMeta(type):
    def __iter__(self):
        return self.by_id.iteritems()

class Foo:
    __metaclass__ = FooMeta
    ...
Answered By: John La Rooy

Try this:

You can create a list with a global scope, define a list in the main module as follows:

fooList = []

Then add:

class Foo:
  def __init__(self):
    fooList.append(self)

to the init of the foo class

Then everytime you create an instance of the Foo class it will be added to the fooList list.

Now all you have to do is iterate through the array of objects like this

for f in fooList:
    f.doSomething()
Answered By: tperera11

You can create a class list and then call append in the init method as follows:

class Planet:
  planets_list = []

  def __init__(self, name):
     self.name = name
     self.planets_list.append(self)

Usage:

p1 = Planet("earth")
p2 = Planet("uranus")

for i in Planet.planets_list:
    print(i.name)
Answered By: P22

You can create a comprehension list and then call member methods as follows:

  class PeopleManager:         
        def __init__(self):       
           self.People = []        
        def Add(self, person):     
           self.People.append(person) 

  class Person:                
        def __init__(self,name,age): 
           self.Name = name          
           self.Age = age   

  m = PeopleManager()
  [[t.Name,t.Age] for t in m.People]

call to fill the object list:

  m = PeopleManager()
  m.Add( Person("Andy",38))
  m.Add( Person("Brian",76))
Answered By: Max Kleiner
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.