Printing all instances of a class
Asked Answered
S

8

91

With a class in Python, how do I define a function to print every single instance of the class in a format defined in the function?

Scarab answered 30/11, 2008 at 13:23 Comment(2)
That's a confusing question. Do you want a method function that formats them all similarly? Or do you want a collection that contains all instances?Speculator
Trying to do something with all instances of a class is almost always a mistake, and a sign that you're not yet used to organizing your objects with data structures. With proper data structures, the correct operation is something like "do [thing] with all elements of this data structure", not "do [thing] with all instances of this class".Monologue
U
127

I see two options in this case:

Garbage collector

import gc
for obj in gc.get_objects():
    if isinstance(obj, some_class):
        dome_something(obj)

This has the disadvantage of being very slow when you have a lot of objects, but works with types over which you have no control.

Use a mixin and weakrefs

from collections import defaultdict
import weakref

class KeepRefs(object):
    __refs__ = defaultdict(list)
    def __init__(self):
        self.__refs__[self.__class__].append(weakref.ref(self))

    @classmethod
    def get_instances(cls):
        for inst_ref in cls.__refs__[cls]:
            inst = inst_ref()
            if inst is not None:
                yield inst

class X(KeepRefs):
    def __init__(self, name):
        super(X, self).__init__()
        self.name = name

x = X("x")
y = X("y")
for r in X.get_instances():
    print r.name
del y
for r in X.get_instances():
    print r.name

In this case, all the references get stored as a weak reference in a list. If you create and delete a lot of instances frequently, you should clean up the list of weakrefs after iteration, otherwise there's going to be a lot of cruft.

Another problem in this case is that you have to make sure to call the base class constructor. You could also override __new__, but only the __new__ method of the first base class is used on instantiation. This also works only on types that are under your control.

Edit: The method for printing all instances according to a specific format is left as an exercise, but it's basically just a variation on the for-loops.

Unconformity answered 30/11, 2008 at 13:56 Comment(8)
+1 for the code samples and demo for weak refs. The garbage collector is interesting but probably not that useful for this type of application.Subtraction
I know, but it's a last resort if everything else should fail. Maybe I should have put it as option 2.Unconformity
IMO the OP asks about __str__() method.Xerography
How would you extend this to include instances of child classes as well?Giroux
Since Python 2.7 this can be done a bit simpler using a WeakSet.Somatology
If I have two classes that both inherit from KeepRefs, then it appears that _refs is common between the two classes. That's not what I wanted, and I didn't realise class inheritance would work that way. Does that mean it's necessary to implement KeepRefs as a metaclass so that each class that uses it gets its own _refs?Risible
Very nice and useful code, but it has a big problem: list is always bigger and it is never cleaned-up, to test it just add print(len(cls.__refs__[cls])) at the end of the get_instances method. ` @classmethod def get_instances(cls): cls.__refs__[cls] = [ instance_ref for instance_ref in cls.__refs__[cls] \ if instance_ref() is not None] for instance_ref in cls.__refs__[cls]: instance = instance_ref() yield instance print(len(cls.__refs__[cls])) `Horta
__refs__...? Don't use dunder :PSubjectivism
A
34

You don't need to import ANYTHING! Just use "self". Here's how you do this

class A:
    instances = []
    def __init__(self):
        A.instances.append(self) # suggested by @Herrivan
print('\n'.join(A.instances)) #this line was suggested by @anvelascos

It's this simple. No modules or libraries imported

Apterous answered 2/7, 2019 at 9:13 Comment(7)
Thanks nice but only works for classes you create. If you want to do this for classes you import from an existing package, you'd have to wrap them with this functionality. Which is doable but you will have to have your whole code instantiate them using your wrapper class and not the original oneOkhotsk
Is just possible to use print('\n'.join(A.instances))? This way, you don't need to create the method printInstance.Tremayne
Can't access the function :( thenSkied
@Okhotsk If that's the case, I am guessing you will create an object of the imported class somewhere in your code (you have imported the class to use it after all). You can make an empty list and after creating the instance of the imported class, append it to the list. example: from Module import ImportedClass\n instances_of_imported_class = []\n # creating as many instances as you want\n for i in range(1000):\n\t instance = ImportedClass()\n\t instances_of_imported_class.append(instance)\n print(instances_of_imported_class)Zobe
In this case, what spoke against writing self.__class__.instances.append(self) as A.instances.append(self)? Isn't it a bit clearer? The magic is simply to store the references in a class attribute, just that, right?Austinaustina
The code won't work correctly in cases A has subclasses. The subclass instances will be stored on the .instances attribute of the subclass, but not on A. In order to also store subclass instances on A, you have to replace self.__class__ by A.Doglike
@JonathanScholbach You're right. Then we should use A.instances.append(self) instead as per @Herrivan 's suggestion. Moreover it is indeed cleanerZobe
R
33

You'll want to create a static list on your class, and add a weakref to each instance so the garbage collector can clean up your instances when they're no longer needed.

import weakref

class A:
    instances = []
    def __init__(self, name=None):
        self.__class__.instances.append(weakref.proxy(self))
        self.name = name

a1 = A('a1')
a2 = A('a2')
a3 = A('a3')
a4 = A('a4')

for instance in A.instances:
    print(instance.name)
Regal answered 27/2, 2012 at 4:26 Comment(2)
Is it possible to print all the class instances without weakref or any other special module? What happen if it is written such as "self.name = name\n self.__class__.instances.append(name)\n" ?Eous
The code won't work correctly in cases A has subclasses. The subclass instances will be stored on the .instances attribute of the subclass, but not on A. In order to also store subclass instances on A, you have to replace self.__class__ by A.Doglike
H
9

Very nice and useful code, but it has a big problem: list is always bigger and it is never cleaned-up, to test it just add print(len(cls.__refs__[cls])) at the end of the get_instances method.

Here a fix for the get_instances method:

__refs__ = defaultdict(list)

@classmethod
def get_instances(cls):
    refs = []
    for ref in cls.__refs__[cls]:
        instance = ref()
        if instance is not None:
            refs.append(ref)
            yield instance
    # print(len(refs))
    cls.__refs__[cls] = refs

or alternatively it could be done using WeakSet:

from weakref import WeakSet

__refs__ = defaultdict(WeakSet)

@classmethod
def get_instances(cls):
    return cls.__refs__[cls]
Horta answered 27/4, 2018 at 13:56 Comment(0)
S
7

Same as almost all other OO languages, keep all instances of the class in a collection of some kind.

You can try this kind of thing.

class MyClassFactory( object ):
    theWholeList= []
    def __call__( self, *args, **kw ):
         x= MyClass( *args, **kw )
         self.theWholeList.append( x )
         return x

Now you can do this.

object= MyClassFactory( args, ... )
print MyClassFactory.theWholeList
Speculator answered 30/11, 2008 at 13:29 Comment(3)
Not quite correct. Some languages offer access to their object memory. In these languages, eg Smalltalk and Ruby, you query ask a class for all its instances. (Actually, I am surprised Python doesnt offer that as well.)Threepence
@Adrian Kuhn: see @NXC's post on Smalltalk's allInstances capability. Maybe Ruby is the last holdout?Speculator
I'd personally rather not see the interpreter suffer extra overhead always providing something which isn't always required, especially when its - as demonstrated - trivially easy to implement when it is.Pownall
S
4

Python doesn't have an equivalent to Smallktalk's #allInstances as the architecture doesn't have this type of central object table (although modern smalltalks don't really work like that either).

As the other poster says, you have to explicitly manage a collection. His suggestion of a factory method that maintains a registry is a perfectly reasonable way to do it. You may wish to do something with weak references so you don't have to explicitly keep track of object disposal.

Subtraction answered 30/11, 2008 at 13:57 Comment(0)
M
2

It's not clear if you need to print all class instances at once or when they're initialized, nor if you're talking about a class you have control over vs a class in a 3rd party library.

In any case, I would solve this by writing a class factory using Python metaclass support. If you don't have control over the class, manually update the __metaclass__ for the class or module you're tracking.

See http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html for more information.

Millstream answered 30/11, 2008 at 20:55 Comment(1)
So unless the class used a custom metaclass, then are you talking about monkey-patching the builtin type class?Plotkin
E
1

In my project, I faced a similar problem and found a simple solution that may also work for you in listing and printing your class instances. The solution worked smoothly in Python version 3.7; gave partial errors in Python version 3.5.

I will copy-paste the relevant code blocks from my recent project.

```
instances = [] 

class WorkCalendar:
    def __init__(self, day, patient, worker):
        self.day = day
        self.patient = patient
        self.worker= worker
    def __str__(self):
        return f'{self.day} : {self.patient} : {self.worker}'

In Python the __str__ method in the end, determines how the object will be interpreted in its string form. I added the : in between the curly brackets, they are completely my preference for a "Pandas DataFrame" kind of reading. If you apply this small __str__ function, you will not be seeing some machine-readable object type descriptions- which makes no sense for human eyes. After adding this __str__ function you can append your objects to your list and print them as you wish.

appointment= WorkCalendar("01.10.2020", "Jane", "John")
instances.append(appointment)

For printing, your format in __str__ will work as default. But it is also possible to call all attributes separately:

for instance in instances:
    print(instance)
    print(instance.worker)
    print(instance.patient)

For detailed reading, you may look at the source: https://dbader.org/blog/python-repr-vs-str

Exemplification answered 7/9, 2020 at 13:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.