Athanassios
Athanassios

Reputation: 206

Iterate over instances of an ITERABLE class

I have defined the following class

class Person(object):        
    _counter = 0
    _instances = []

    def __init__(self, nam):
        self.name = nam
        self._instances.append(self)
        self._id = Person._counter + 1
        Person._counter+=1        

    def __repr__(self):
        return f'{self._id} : Person({self.name})'    

    def __iter__(self):
        return self

    def __next__(self):
        pass

    @classmethod
    def iterate(cls):
        return [p for p in Person._instances]

I can iterate over the instances of this class with

[p for p in Person._instances]

I would like to learn how it is possible to arrive at the same result by calling

[p for p in Person]

I have searched many similar questions and I tried out several suggested answers, without any luck. I am using Python 3.6 Anaconda x64 and in all the solutions I tested on the interpreter I am getting back

  [p for p in Person]
  TypeError: 'type' object is not iterable

Could you please write exactly how I can execute successfully this [p for p in Person] call to get back the same list as Person.iterate() and Person._instances ?

Upvotes: 4

Views: 691

Answers (2)

Athanassios
Athanassios

Reputation: 206

For clarity purposes I have included the solution and some testing data as it has already been pointed out by "Moses Koledoye" and there is also a useful comment by "chepner" which I tried to take on account.

Solution

class PersonMeta(type):
    _counter = 0     
    _instances = []

    def __iter__(self):
        return iter(getattr(self, '_instances', []))


class Person(metaclass=PersonMeta):

    def __init__(self, nam):
        self.name = nam
        Person._instances.append(self)
        self._id = Person._counter + 1
        Person._counter+=1        

    def __repr__(self):
        return f'{self._id} : Person({self.name})'    

Output

In [70]: [p for p in Person]
Out[70]: 
[1 : Person(James),
 2 : Person(George),
 3 : Person(Jack),
 4 : Person(Mary),
 5 : Person(Sue),
 6 : Person(James),
 7 : Person(George),
 8 : Person(Jack),
 9 : Person(Mary),
 10 : Person(Sue)]

Ideally, I would like to see a more clear separation between the Metaclass and the Class. But I don't know how I can transfer the folowing statements to the MetaPerson. Is that a good idea ? Can you help me with this ?

Person._instances.append(self)    
Person._counter+=1 

Upvotes: 0

Moses Koledoye
Moses Koledoye

Reputation: 78536

You can define an __iter__ on the metaclass to make the metaclass instance (i.e. Person) iterable:

class metaclass(type):
   def __iter__(cls):
       return iter(getattr(cls, '_instances', []))

class Person(metaclass=metaclass):  
    # __metaclass__ = metaclass Python 2
    ...

Upvotes: 9

Related Questions