Is it always bad to collect all instances in a class variable?
Asked Answered
B

3

6

Consider two versions of a simple weather model which stores the location of clouds:

class cloud:
    def __init__(self, x, y):
        self.x = x
        self.y = y

collection = []
collection.append(cloud(1,2))
collection.append(cloud(4,6))

def update_all_clouds(collection):
    for c in collection:
        cloud.x += 1
        cloud.y += 1

update_all_clouds(collection)

vs

class cloud:
    collection = []
    def __init__(self, x, y)
        self.x = x
        self.y = y
        cloud.collection.append(self)
    @classmethod
    def update_all(cls):
        for c in cloud.collection:
            c.x += 1
            c.y += 1
cloud(1,2)
cloud(4,6)
cloud.update_all()

This has basically been punished here Is it bad to store all instances of a class in a class field? but there is an emphasis here on class methods which act on all instances. Is there nothing to be said for the simplicity of the last three lines that the second approach affords?

I am aware that another approach would be creating a list-like class called, for example, collection and giving that class methods like update_all() but to me it doesn't seem much better.

Brunet answered 9/7, 2015 at 6:20 Comment(0)
A
3

This question can simply be reduced to a question about using global variables, as a mutable class-level member is just a globlal variable in a different namespace.

All the arguments against using global variables apply to this as well.

Aestheticism answered 9/7, 2015 at 6:29 Comment(1)
This is actually worse than just a global variable. It would have been “just” a global variable, if one would call Cloud.addObject(cloudObj), but here the objects are added automatically.Tenrec
T
3

In general, this is bad, yes, for the simple reason that the objects being in a list keeps a reference to them pretty much forever. There being a reference to an object prevents it from being garbage collected. So objects of your type essentially live forever (until the program terminates) and the memory they take up will never be freed.

Of course, if you have a very specific need for this, and are in full control of when the objects are created, you could do it like that. But in general explicit is better than implicit, so it’s a better idea to have an actual collection where you add those elements. That collection could even live in the type, so you could do:

obj = Cloud(…)
Cloud.add(obj)

# or even
obj = Cloud(…).persistInType()

You could also use weak references to avoid the problem described above, but that’s additional overhead, and a bit more complicated to manage. So just do yourself a favor, and collect the objects manually.

Tenrec answered 9/7, 2015 at 6:30 Comment(3)
will having an optional argument add_to_collection=False on init so that adding the object to the collection is checked against that variable makes things explicit ?Siccative
I guess that could work. But you should also add some functionality to be able to dispose the object, removing it from the list.Tenrec
The code I posted is a simplification. The collection is constantly getting culled @TenrecBrunet
C
3

Considering that explicit is better than implicit (see The Zen of Python) perhaps the best is approach is to have two classes: Cloud and CloudCollection. This would allow you to write code like this:

collection = CloudCollection()
collection.add(Cloud(1, 2))
collection.add(Cloud(4, 6))
collection.shift(1, 1)
Chiles answered 9/7, 2015 at 6:39 Comment(3)
As per “Simple is better than complex”, and “Special cases aren't special enough to break the rules”, you would just use a normal list there though ;)Tenrec
As soon as we can see the "collection" as a standalone entity with its own "manipulators" it's appropriate to use a dedicated class for it, in my opinion. I don't say it's necessarily the OP's case.Chiles
ended up doing "class collection(list)" and "clouds = collection() etc"Brunet

© 2022 - 2024 — McMap. All rights reserved.