Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I iterate over a class in Python?

Tags:

python

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.

like image 802
xorsyst Avatar asked May 30 '12 10:05

xorsyst


People also ask

What can you iterate over in Python?

These include the string, list, tuple, dict, set, and frozenset types. But these are by no means the only types that you can iterate over. Many objects that are built into Python or defined in modules are designed to be iterable.

Can you iterate over set Python?

In Python, Set is an unordered collection of data type that is iterable, mutable and has no duplicate elements. There are numerous ways that can be used to iterate over a Set.

What does __ ITER __ do in Python?

The __iter__() function returns an iterator for the given object (array, set, tuple, etc. or custom objects). It creates an object that can be accessed one element at a time using __next__() function, which generally comes in handy when dealing with loops.


1 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.

like image 136
glglgl Avatar answered Oct 10 '22 15:10

glglgl