Lightbreeze
Lightbreeze

Reputation: 4974

Is it Pythonic to have a class keep track of its instances?

Take the following code snippet

class Missile:
    instances = []

    def __init__(self):
        Missile.instances.append(self)

Now take the code:

class Hero():
    ...
    def fire(self):
        Missile()

When the hero fires, a missile needs to be created and appended to the main list. Thus the hero object needs to reference the list when it fires. Here are a few solutions, although I'm sure there are others:

I didn't post this on gamedev because my question is actually more general: Is the previous code considered okay? Given a situation like this, is there a more Pythonic solution?

Upvotes: 3

Views: 279

Answers (5)

Pieter Witvoet
Pieter Witvoet

Reputation: 2833

Letting a character keep track of the projectiles they fired can work fine, but it also means that whenever you remove that character, it's projectiles are gone as well. If I remember correctly, that's what happened in Tiberian Sun when you destroyed a hover MLRS - if it had any missiles in flight when it exploded, they would disappear.

Letting a characters update function return the projectile(s) it created, so the update loop can put those in a projectiles list, can also work just fine. Handling multiple projectiles per update can be as simple as always returning a list - it can be empty or it can contain one projectile or more.

Here's what I would propose: store the projectiles and characters (hero, allies, enemies, and so on) in a World object, and pass a reference to that object to every character that needs to interact with it (by launching projectiles into the world, or by checking for nearby enemies). For every missile your hero launches, he calls his world's addProjectile function. If the hero needs to perform other actions that affect the rest of the game, the World object can provide functionality for that, without you having to clutter the main update loop with special cases.

Of course, you're not limited to one approach only. You can combine them whenever you need to. If you have homing projectiles, you can give them a reference to their target, so they can update their velocity with each update call. If you have projectiles that can't damage the one that fired them, you can give them a reference to their 'owner'. If a character can only have 2 projectiles at the same time (as in some old games), you can have characters keep track of their active projectiles, so they know when to stop firing. You can even let projectiles keep track of their 'friends' if they're fired in a burst, so they can coordinate their movement to form fancy flocking patterns. ;)

Upvotes: 1

Ethan Furman
Ethan Furman

Reputation: 69051

Questions:

  • Does the Hero have any further control over the missile once launched?
    • Yes -> keep list with hero instance
    • No -> keep it somewhere else
  • Does the missile class need to know about already created missiles to create a new missile, or to otherwise process missiles?
    • Yes -> keep list with missile class
    • No -> keep it somewhere else
  • Did you answer No to both of the above questions?
    • Yes -> keep the list as part of the game

There is nothing unPythonic about keeping the list as part of the class if your reasons for doing so make sense.

Upvotes: 4

Lee Netherton
Lee Netherton

Reputation: 22512

Regardless of being Pythonic, I would say that good OO practice would require that the Hero class maintains the list of missiles.

(Unless I'm missing something, and one Missile would need to be aware of the others?)

Something like this might be appropriate:

class Hero():
    def __init__(self):
        self.missiles = []

    def fire(self):
        self.missiles.append(Missile())

If you need a 'global' list of missiles rather than one for each Hero, then I would suggest creating a static member variable instead:

class Hero():
    missiles = []

    def fire(self):
        Hero.missiles.append(Missile())

Upvotes: 2

mac
mac

Reputation: 43041

Your code is OK, and the first two solutions you suggested are acceptable (don't really understand the third one completely though: what list is "the list"?).

Depending on your needs, and if there are many classes needing this kind of tracking system (like bullets, rockets, mines...) you could move this tracking logic into a metaclass (classes used to instantiate classes).

In essence, you subclass the type class and create your own "class constructor", then you use the syntax:

__metaclass__ = my_metaclass_that_generate_classes_tracking_their_instantiation

in your class-to-be-tracked.


EDIT: just read your comment to the original question. If you have more entities firing missiles of the same class, then the cleanest pattern to follow - IMO - would be to have ech entity (Hero, BadGuy, Allied...) to keep themselves a list of missile objects. It models the world you are describing closely and it will make code maintenance easier...

HTH!

Upvotes: 1

John La Rooy
John La Rooy

Reputation: 304215

I've done it in the past for basically the same reason as you. I really don't see any problem with it

Perhaps in your case it makes more sense for the list to belong to the hero object though

Upvotes: 2

Related Questions