5

Well simply making a class iterable is easy enough using meta classes (so some other answers here). However I wish to make a class iterable, and also enabling one to "iterate a subgroup based on inheritance". An example of my use:

class IterPartRegistry(type):
    def __iter__(cls):
        return iter(cls._registry)


class A(object, metaclass=IterPartRegistry):
    _registry = []
    def __init__(self, name):
        self.name = name
        self._registry.append(self)

class B(A):
    pass

class C(A):
    pass


A("A - first")
B("B - first")
B("B - second")
C("C - first")

for t in A:
    print(t.name)

print(" --- ")
for t in B:
    print(t.name)

exit()

The first loop works - it iterates over all instances and childs of "A". However the second loop should only run over the specific subgroup of "A" - those that are instances of the child "B" (or children further down the line).

(How) can this be achieved easiest? In such a way adding more subclasses require least amount of work/change?

paul23
  • 8,799
  • 12
  • 66
  • 149

2 Answers2

3

You can use isinstance to insure that you are getting only class instances

In your code its a one line change:

class IterPartRegistry(type):
    def __iter__(cls):
        return (c for c in cls._registry if isinstance(c, cls))
Yoav Glazner
  • 7,936
  • 1
  • 19
  • 36
  • This looks really good. Just wondering - when iterating won't it iterate the _registry each time I go to the next element. (in other words iterating n elements now takes O(n^2) instead of O(n) time? – paul23 Jan 16 '16 at 21:50
  • 1
    since the iterator returned by a generator is the generator itself, maybe that could simply be `return (c for c in cls._registry if isinstance(c, cls))` – Pynchia Jan 16 '16 at 21:54
  • @paul23, No it would only iterate once, O(n) – Yoav Glazner Jan 16 '16 at 23:03
2

You could let each class maintain its own list of instances by giving each its own _registry class attribute. Then, instead of checking if each instance is of a particular class, you could instead iterate over all the values in the _registrys for each subclass of cls. To find those subclasses you could use the cls.__subclasses__() method:

import itertools as IT
class IterPartRegistry(type):
    def __init__(cls, name, bases, attrs):
        super(IterPartRegistry, cls).__init__(name, bases, attrs)
        cls._registry = []
    def __iter__(cls):
        yield from cls._registry
        for subcls in cls.__subclasses__():
            yield from subcls

class A(object, metaclass=IterPartRegistry):
    def __init__(self, name):
        self.name = name
        self._registry.append(self)

class B(A): pass

class C(A): pass

class D(B, C): pass

A("A - first")
B("B - first")
B("B - second")
C("C - first")
D("D - first")

for t in A:
    print(t.name)

print(" --- ")
for t in B:
    print(t.name)

yields

A - first
B - first
B - second
D - first
C - first
D - first
 --- 
B - first
B - second
D - first
unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
  • Oops, my mistake. That can be fixed by iterating over the subclasses recursively. I've edited the post to show what I mean. – unutbu Jan 17 '16 at 13:05