Python: példányok nyilvántartása osztályon belül

Egy játékban szeretném, ha az ellenségek osztálya nyilvántartaná a létező ellenségeket, és szeretném, hogy ha a példányok meghalnak, akkor törölnék magukat. Szerintetek jól végzi-e el ezt a feladatot az alábbi program?

https://pastebin.com/7Bi3AAci

Ha nem, mi vele a baj? Azért vagyok bizonytalan mert jó sok cikk szerint erre weak refernces való (pl: https://stackoverflow.com/questions/37232884/in-python-how-to-remove-an…).

Hozzászólások

Nem néztem meg a programodat, de miért kellene egy osztálynak ismernie a létező példányait?
És mit értesz pontosan az alatt, hogy az osztály? Az összes példány osztozni fog egy közös listán?

Persze lehet, hogy nem értem az alap problémát. Esetleg leírnád, hogy mit is szeretnél megoldani?

"És mit értesz pontosan az alatt, hogy az osztály? Az összes példány osztozni fog egy közös listán?"

https://pastebin.com/7Bi3AAci

"Esetleg leírnád, hogy mit is szeretnél megoldani?"

Egy játékban szeretném, ha az ellenségek osztálya nyilvántartaná a létező ellenségeket, és szeretném, hogy ha a példányok meghalnak, akkor törölnék magukat.

Klasszik OOP szerint:

- csinálnék egy listát amiben ellenség típusú objektumok lehetnek
- ez a lista legyen egy singleton

--
Gábriel Ákos

Hozz létre egy __del__(self) metódust, ami kitörli a nyilvántartásból az objektumát, mielőtt a szemétgyűjtő törölné magát az objektumot.

Szerk.: Ez mégsem oldja meg automatikusan a problémát, mert a __del__() meghívásának időpontja nem látható előre.
--
eutlantis

Valami ilyesmit tennék:


#!/usr/bin/env python3
#-*- coding:utf-8 -*-

class Soldier():
 
    instances = {} # name:self
 
    def __init__(self, name):

        self.name= name
        if name in self.instances:
                raise NameError("Duplikált név: '"+name+"'")
        else:
            self.instances[name]=self

    @classmethod
    def isalive(cls,name):
        return name in cls.instances

    def fight(self):
        if not self.isalive(self.name):
            return None
        else:
            # ....
            return True

    def shoot(self):
        if not self.isalive(self.name):
            return None
        else:
            # ....
            return True

    @classmethod
    def trupp(cls):
        return list(cls.instances.keys())

    @classmethod
    def kill(cls,name):
        ident= cls.instances.pop(name,None)

if __name__ == "__main__":

    enemy=[ Soldier(name) for name in ['Alpha', 'Beta', 'Gamma'] ]
    print( Soldier.trupp()) 

    try:
        alpha2=Soldier('Alpha')
    except NameError as event:
        print(event)

    Soldier.kill('Gamma')
    print( Soldier.trupp())

    jedi=Soldier("Jedi")
    print( Soldier.trupp())
    print( jedi.fight() )
    jedi.kill("Jedi")  # öngyilkosság?
    print( jedi.fight() )

És egy katona bármit is tenne (fight,shoot), előtte megnézné, hogy egyáltalán még életben van-e. Még meghalni is csak akkor szabad, ha még él:-)
--
eutlantis

Mert rugalmatlan, tightly-coupled.
Mi van akkor, ha több alrendszernek is kezelnie kell azt a dolgot, hogy "meghalt egy ellenség"?
Leírod a "halálkezelő" kódba, hogy történjen X meg Y , meg Z? Nem kiterjeszthető a kódod, valamint az Ellenség osztály tud arról, hogy a rendszer rajta kívül milyen komponensekből áll.

Míg ha eseménykezelés van:
1. Meghal egy ellenség, emittál egy "MEGHALT_EGY_ELLENSÉG" eseményt.
2. Akit érdekel a "MEGHALT_EGY_ELLENSÉG" esemény (pl. UI, ellenségek listája, bárki más), elkapja ezt az eseményt, és cisnál vele, amit akar.

A jövőben, ha új viselkedést akarsz hozzátenni a rendszerhez, ami a "MEGHALTAM" eseményre csinál valamit, NEM kell belenyúlnod az Ellenség osztály kódjába.

a linkelt SO kérdésben olyat csinálnak, hogymikor a GC szabadítaná fel az objektumot, akkor kerüljön ki a listából.

Ha jól értem, neked jól megfogható halál eseményed van, amikor ki tudod szedni kézzel a listából - és minden referenciát megszüntetsz rá, a GC meg majd összekaparja valamikor. Ehhez nem kell weakreference, mehet az az út, amit elkezdtél.

Lehet, hogy öreg vagyok már, de nem statikus metódust nem egy példányon keresztül hívni elég betegnek tűnik. @classmethod helyett miért nem @staticmethod van? A this(itt cls) paraméter meg nem kell, hisz ezek nem egy példányhoz kötött metódusok.

Én így csinálnám:

https://pastebin.com/RU5GG0Pk

Azért nem Test.függvénynév néven hívom, mert akkor baj van, ha esetleg alosztályba kerülnek ezek.
Amikor classmethod-ot hívok self-fel, azt csak úgy tudom, lévén a függvény belsejében nincs cls-em, a self-nek persze szintén nincs meg az a metódusa, de majd meghívódik az osztályé. Engem is zavar, épp ezért filózok el a self.__class__.függvény híváson.

És hogy miért nem staticmethod? Akár jó is lehet. Ugye úgy tudom, hogy akkor használ az ember staticot és nem class-t, ha van ugyan köze a függvénynek az osztályhoz, de nem használja a benne lévő cuccokat, csak logikailag jó, ha ott a függvény és nem máshol. Itt meg ugye használjuk az osztályváltozót (a listát), hát ezért. De ez csak válasz a kérdésedre, nem a tuti igazság...

"Normális" programnyelvekben (C++ pl.) meg sem hívhatsz az osztály nevével nem statikus metódust (hisz nem lesz this (self, cls)). Az a lista az oszályhoz tartozik, nem minden egyes példányhoz, így az azt kezelő metódusok is statikusak, főleg, hogy Test:xxxx-ként hívod őket, nem pedig a=Test(), a.xxx. Igaz, pythonban itt lehet gányolni, csak minek, főleg, hogy a self tök felesleges ezekhez a metódusokhoz.

Hát én már nagyon rég átnyergeltem Go-ra, úgyhogy előre is bocsi.
De anno se szerettem ezeket a típusokat, osztályokat, öröklődést - sokszor megégettem magam...

Ha jól látom, itt egy darab listába (dict, ha gyorsabbat akarsz) kell karbantartani az élő példányokat.

Tehát kell egy globális vagy csilivili osztály PÉLDÁNY aminek a pop/push metódusát meg kell hívni __init__ és kill esetén.
Én ezt nem keverném bele az osztályba, simán kapja meg az __init__-ben hogy melyik dict-ben kell ezt nyilvántartani, és kész.

Mert a single responsibility jó dolog:
https://en.wikipedia.org/wiki/Single_responsibility_principle

Amúgy meg kérdés, hogy mi a célod az alkalmazással, ha nem érdekel a kódminőség, akkor mindegy, nem kell ilyen kérdésekkel foglalkoznod, de akkor meg ugye miért tetted volna fel a kérdést egyáltalán...

Azt nem értem a wikipédia-cikk pédája alapján, hogy meddig atomizáljuk a felelősségeket: aszondja, hogy a jelentést az egyik osztály compile-ja. Na de ez is szétszedhető még: az egyik osztály kinyeri a szükséges adatokat, a másik kiszámolja, amit kell.

Valamilyen értelemben nálam is s.r. van: egy osztály dolga minden, ami azzal az osztállyal kapcsolatos: megy, lő, ütközik, meghal és törlődik.

Vagy valamit nem értek?

Halihó!

Megy, lő, ütközik, igen, ezek megfelelnek az SR-nek. A “meghal”, talán belefér, de ez már neccesebb, attól függ milyen mellékhatásai vannak a "halálnak". A törlődik, viszont semmiképp. Ha a törlést is az osztályra bízod, akkor gyakorlatilag felruházod a “kezeld az erőforrásokat” responsibilityval.

Én csinálnék pl. egy “EnemyManager” osztályt, ahol tárolnám a pédányokat. Ez az osztály végezné az ellenségek spawnolását, nyilvántartását és törlését is.

A törlés ugyanazért nem illik ide, amiért a spawn sem (ami helyesen az osztályon kívül, a 28. sorban van a kódodban).

Alapvetően "jóvanazúgy," ha az neked megfelel, csak majd visszaüthet ha megnő. De akkor úgyis igazodsz.
A static metódusokat szokták kerülni, ha lehetséges, mivel az öröklődéssel nem működik szépen együtt.
Itt ez megoldható lenne egy külső osztállyal, akinek pont a lista nyilvántartása lenne a feladata és lenne két nagyon egyszerű osztály. Az egyszerűség jó.

Ami nekem személy szerint sokkal jobban szúrja a szememet, az a printelős tesztelés. Nem számottevően nehezebb unittest-esen megfogalmazni, viszont sokkal hasznosabb.

_______Peter
I am a stone. I do not move.