Search code examples
pythonclassobjectattributes

create a list of an objectA inside the objectA


When I'm trying to append() the object itself into a list inside the object, it doesn't work, I don't understand why.

class PLayer:

    CLASS_NAME = "player"
    TOTAL_PLAYER_NUMBER = 0
    TOTAL_PLAYER_LIST = []
    PLAYER_ID_INCREMENT = 0

    def __init__(self, name,
                 first_name,
                 birthday,
                 note,
                 player_id=None,
                 total_score=None,
                 tournament_score=None):

        self.PLAYER_ID_INCREMENT += 1
        self.TOTAL_PLAYER_NUMBER += 1
        self.name = name
        self.first_name = first_name
        self.birthday = birthday
        self.player_id = self.PLAYER_ID_INCREMENT
        self.total_score = 0
        self.tournament_score = 0
        self.note = note
        self.TOTAL_PLAYER_LIST.append(self.Player)

So the class Player have a list TOTAL_PLAYER_LIST = [], then inside the __init__ I want to add the player that have been freshly created to the list with the last line self.TOTAL_PLAYER_LIST.append(self.Player) but I get this error

line 25, in __init__
    self.TOTAL_PLAYER_LIST.append(self.Player)
AttributeError: 'PLayer' object has no attribute 'Player'

I don't understand why. I've tried to put and remove self., the parenthesis also () for Player() .


Solution

  • Remove the .Player attribute

    If you do this, it works fine. Your class doesn't have any Player attribute. The player you want to add is referenced in the self variable, the instance.

            self.TOTAL_PLAYER_LIST.append(self)
    

    Let's see a simpler example with debug:

    class Player:
        TOTAL_PLAYER_NUMBER = 0
        TOTAL_PLAYER_LIST = []
        PLAYER_ID_INCREMENT = 0
    
        def __init__(self, name):
            self.PLAYER_ID_INCREMENT += 1
            self.TOTAL_PLAYER_NUMBER += 1
            self.name = name
            self.player_id = self.PLAYER_ID_INCREMENT
            self.TOTAL_PLAYER_LIST.append(self)
    
        def __repr__(self):
            return f"{self.name}:<{self.player_id}>"
    
    print(Player("Doe"))  # Doe:<1>
    print(Player.TOTAL_PLAYER_LIST)  # [Doe:<1>]
    

    Other critical issue with your code

    1. Name your class Player, not PLayer

    2. Class attribute vs Instance attribute

            self.PLAYER_ID_INCREMENT += 1
    

    This won't change the class attribute PLAYER_ID_INCREMENT, just create an instance attribute of 1:

    doe = Player("Doe")
    joe = Player("joe")
    print(Player.TOTAL_PLAYER_NUMBER)  # 0
    print(doe.TOTAL_PLAYER_NUMBER)  # 1
    print(joe.TOTAL_PLAYER_NUMBER)  # 1
    

    Fix this with:

        def __init__(self, name):
            Player.PLAYER_ID_INCREMENT += 1
            Player.TOTAL_PLAYER_NUMBER += 1
            ...
    
    doe = Player("Doe")
    joe = Player("joe")
    print(Player.TOTAL_PLAYER_NUMBER)  # 2
    print(doe.TOTAL_PLAYER_NUMBER)  # 2
    print(joe.TOTAL_PLAYER_NUMBER)  # 2
    

    This happens because integers are immutable and += will "rebind" the incremented value to a new reference located... in self, the instance namespace, not the class namespace.

    A more pythonic way to do that is with metaclasses:

    import typing
    
    
    class CountInstances(type):
        def __init__(cls, name, bases, dict_):
            super().__init__(name, bases, dict_)
            cls.count = 0
    
        def __call__(cls, *args, **kwargs):
            cls.count += 1
            return super().__call__(*args, **kwargs)
    
    
    class Player(metaclass=CountInstances):
        total_player_list: typing.Final = []
    
        def __init__(self, name):
            self.name = name
            self.player_id = self.count
            self.total_player_list.append(self)
    
        def __repr__(self):
            return f"{self.name}:<{self.player_id}>"
    
    
    doe = Player("Doe")
    joe = Player("Joe")
    print(Player.count)  # 2
    print(doe.count)  # 2
    print(joe.count)  # 2
    print(Player.total_player_list)  # [Doe:<1>, Joe:<2>]
    

    More information about metaclasses: What are metaclasses in Python?