theChef613
theChef613

Reputation: 125

Getting all instances of a specific type within a class

Is there a way to get a list or return all the instances of a class within a class in python? I've already done some research into it and all the answers that I've seen assume the instances are global variables, not part of an existing class.

For example, can I find all instances of Bar that are instantiated within the class Foo below?

class Foo:
   def __init__(self):
      self.mymsg1 = Bar('John Doe')
      self.mymsg2 = Bar('Jane Doe')
      self.somenumber = 42
      self.somewords = 'hello world'

class Bar:
   def __init__(self, name):
      self.hellomsg = 'hello ' + name

I want to be able to get mymsg1 and mymsg2 because they are Bar objects, but I don't want any of the other attributes or methods.

Upvotes: 3

Views: 130

Answers (3)

MMHer
MMHer

Reputation: 21

This way you could filter whatever the class you pass by argument, and the function returns an array of that class objects.

class Foo:
    def __init__(self):
      self.mymsg1 = Bar('John Doe')
      self.mymsg2 = Bar('Jane Doe')
      self.somenumber = 42
      self.somewords = 'hello world'
      
    def filter_class(self, _class):
      return [i[1] for i in (self.__dict__).items() if isinstance(i[1],_class)]
      
  class Bar:
     def __init__(self, name):
        self.hellomsg = 'hello ' + name

Upvotes: 1

FazeL
FazeL

Reputation: 986

You can use class variable very easily:

class Foo:
   def __init__(self):
      self.mymsg1 = Bar('John Doe')
      self.mymsg2 = Bar('Jane Doe')
      self.somenumber = 42
      self.somewords = 'hello world'

class Bar:
   _instances = []
   def __init__(self, name):
      Bar._instances.append(self)
      self.hellomsg = 'hello ' + name

>>> f = Foo()
>>> print('first:', Bar._instances[0].hellomsg,
...       ', second:' ,Bar._instances[1].hellomsg)                                                          
first: hello John Doe , second: hello Jane Doe

For partitioning the Bar instances by instantiator, one can do this:

from collections import defaultdict

class Foo:
   def __init__(self):
      self.mymsg1 = Bar('John Doe', self)
      self.mymsg2 = Bar('Jane Doe', self)
      self.somenumber = 42
      self.somewords = 'hello world'

class Bar:
   _instances = defaultdict(list)
   def __init__(self, name, instantiator):
      Bar._instances[instantiator].append(self)
      self.hellomsg = 'hello ' + name

>>> f, ff = Foo(), Foo()
>>> print('first:', Bar._instances[f][0].hellomsg,
...       ', second:' ,Bar._instances[ff][1].hellomsg)                                                          
first: hello John Doe , second: hello Jane Doe

Upvotes: 2

Mateen Ulhaq
Mateen Ulhaq

Reputation: 27201

The simplest and most effective method is to create an appropriate data structure and avoid doing Python magic. However, if you have a degree in serpentine magic, I have listed various methods of advanced wizardry below.

Search __dict__

class Foo:
    def __init__(self):
        self.bar1 = Bar()
        self.bar2 = Bar()

    def bars(self):
        return [v for v in self.__dict__.values() if isinstance(v, Bar)]

Manually register each name

class Foo:
    def __init__(self):
        self._bar_names = {"bar1", "bar2"}
        self.bar1 = Bar()
        self.bar2 = Bar()

    def bars(self):
        return [self.__dict__[x] for x in self._bar_names]

Automatically register names by overriding __setattr__

class Foo:
    def __init__(self):
        self._bar_names = set()
        self.bar1 = Bar()
        self.bar2 = Bar()

    def __setattr__(self, name, value):
        if name == "_bar_names":
            self.__dict__[name] = value
            return
        if isinstance(value, Bar):
            self._bar_names.add(name)
        else:
            self._bar_names.discard(name)
        self.__dict__[name] = value

    def bars(self):
        return [self.__dict__[x] for x in self._bar_names]

Upvotes: 1

Related Questions