python-3.xattributesstatic-methodspython-classin-class-initialization

Using external functions to create the attributes of a class object always returning same values


During the process of making a simple football game, I have run across an issue I have not had before with class object initialisation, and can't find a good solution. As seen in the main method at the bottom of the code, iteratively creating new instances of Player() always share the same instance attribute values, despite these values being initialised within init. I know this must have to do with the use of the external methods as default values but I don't understand why these values would not be unique as they are not class attributes, they are instance attributes... right?

# import dependencies
import random
from position import Position


# external methods
def file_to_list(url: str) -> list[str]:
    f = open(url, 'r')
    all_lines = []
    for line in f:
        stripped_line = (line.strip())
        all_lines.append(stripped_line)
    f.close()
    return all_lines


def get_random_name() -> str:
    first_names: list[str] = file_to_list("assets/boys_names.txt")
    last_names: list[str] = file_to_list("assets/last_names.txt")
    return random.choice(first_names) + ' ' + random.choice(last_names)


def get_random_age() -> int:
    return random.randint(17, 37)


def get_random_nationality() -> str:
    dice: int = random.randint(1, 3)
    if dice == 1:
        return "British"
    elif dice == 2:
        return random.choice(
            ["French", "Spanish", "Italian", "Brazilian", "Portuguese", "Irish", "Danish", "Dutch", "German", "Belgian",
             "Argentinian"])
    else:
        nationalities: list[str] = file_to_list("assets/nationalities.txt")
        return random.choice(nationalities)


def get_random_score() -> float:
    base: float = float(random.randint(1, 10))
    if base == float(10):
        return base
    else:
        decimal: float = random.uniform(0, 1)
        return round(base + decimal, 1)


# class definition
class Player:
    def __init__(self, name: str = get_random_name(), position: Position = None, age: int = get_random_age(),
                 nationality: str = get_random_nationality(), attack: int = get_random_score(),
                 defense: int = get_random_score()):
        self.__name: str = name
        self.__position: Position = position
        self.__age: int = age
        self.__nationality: str = nationality
        self.__attack: float = attack
        self.__defense: float = defense
        self.__skill: int = int(((self.__attack + self.__defense) / 2) * 10)

    @property
    def name(self):
        return self.__name

    @property
    def position(self):
        return self.__position

    @property
    def age(self):
        return self.__age

    @property
    def nationality(self):
        return self.__nationality

    @property
    def attack(self):
        return self.__attack

    @property
    def defense(self):
        return self.__defense

    @property
    def skill(self):
        return self.__skill

    def __str__(self) -> str:
        print("Name: " + self.__name)
        if not self.__position:
            print("Position not set.")
        else:
            print("Position: " + self.__position.name)
        print("Age: " + str(self.__age))
        print("Nationality: " + self.__nationality)
        print("Skill: " + str(self.__skill))
        print("Attack: " + str(self.__attack))
        return "Defense: " + str(self.__defense)


if __name__ == "__main__":
    player_list: list[Player] = []
    for i in range(3):
        new: Player = Player() #if i pass in a value it will not be shared.
        player_list.append(new)

    for p in player_list:
        print(p)

Every time a player is created they share all the same values that are not passed in. Example output:

Name: Royal Vega
Position not set.
Age: 36
Nationality: British
Skill: 48
Attack: 1.2
Defense: 8.5
Name: Royal Vega
Position not set.
Age: 36
Nationality: British
Skill: 48
Attack: 1.2
Defense: 8.5
Name: Royal Vega
Position not set.
Age: 36
Nationality: British
Skill: 48
Attack: 1.2
Defense: 8.5

Instead of this, the expected output would return 3 unique objects of randomised age, name and skills as suggested in the code above.


Solution

  • "In Python function parameters can have default values. These default values are expressions which are executed when the function is defined, i.e. only once. The same default value will be used every time the function is called, thus modifying it will have an effect on every subsequent call. This can create some very confusing bugs."

    You need to make the default values None and in the constructor add if statements such as:

    if name == None:
        self.__name = get_random_name()