create a list of an objectA inside the objectA

Question:

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() .

Asked By: Matteo

||

Answers:

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?

Answered By: Dorian Turba
Categories: questions Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.