'Can't loop through dict_keys in @property method in python 3?

So I have the following code:

@property
def mod_list(self) -> List[Modifier]:
    mods = []
    
    print(len(self.statuses)) #Prints 0??? Update method prints the actual number when called??? Also means it *is* getting called properly when it's getting accessed

    for status in self.statuses: # I've tried calling the keys() method on the dict but that doesn't work either
        print("hello") #Doesn't print, indicating that it isn't looping
        mods.extend(status.mods) # Note: statuses dict uses StatusEffect objects as keys, with values being the number of turns left before that status is removed; StatusEffects all possess a 'mods' property that is initialized to '[]' and can only be made up of modifiers
    
    return mods

I don't understand why it can't access the keys of the dict? Even if I remove the decorator and call it instead of accessing it?

Especially when this method works properly?

def update(self):
    deletion = []
    print(len(self.statuses)) #Prints actual number of keys????
    for name in self.statuses.keys():
        print(name.name, self.statuses[name]) #Prints normally whenever update is called???
        if hasattr(name, "turn_effect"):
            name.turn_effect(self.entity)
        self.statuses[name] -= 1
        if self.statuses[name] < 1:
            deletion.append(name)
        ...

    for status in deletion:
        del self.statuses[status]

Why isn't it working properly? And how do I fix it?

Edit: I managed to recreate the issue below, I think it might have to do with 'deepcopy' in the spawn method since I couldn't recreate the issue from scratch until I implemented and used the spawn method.

from __future__ import annotations

from typing import Dict, List

from copy import copy, deepcopy

class Entity:
    def __init__(self, name:str, **kwargs:Component):
        self.name = name
    
        self.components:Dict[str, Component] = {}
    
        for name, component in kwargs.items():
            self.add_component(name, component)

    def add_component(self, name:str, component:Component):
        self.components[name] = component
        component.entity = self

    def update(self):
        for comp in self.components.values():
            comp.update()

    def spawn(self):
        return deepcopy(self)


class Component:
    __entity: Entity

    @property
    def entity(self) -> Entity:
        return self.__entity

    @entity.setter
    def entity(self, entity:Entity):
        if hasattr(self, "__entity") and self.__entity is not None:
            self.entity.remove_component(self)
        self.__entity = entity

    def update(self):
        """Placeholder method for component update methods"""


class StatusList(Component):
    entity: Entity
    def __init__(self) -> None:
        self.statuses:Dict[StatusEffect, int] = {}

    def add_status(self, status:StatusEffect, turns:int=1):
        self.statuses[status] = turns

    def update(self):
        deletion = []
        print(len(self.statuses.keys()))
        for name in self.statuses.keys():
            print(name.name, self.statuses[name])
            if hasattr(name, "turn_effect"):
                name.turn_effect(self.entity)
            self.statuses[name] -= 1
            if self.statuses[name] < 1:
                deletion.append(name)

        for status in deletion:
            del self.statuses[status]

    @property
    def mod_list(self) -> List[Modifier]:
        mods = []
    
        print(len(self.statuses))

        for status in self.statuses:
            print("hello")
            mods.extend(status.mods)
    
        return mods


class StatusEffect:
    name:str
    turn_effect: function
    mods:List[Modifier] = []

    def apply(self, entity:Entity, turns:int=1):
        if "status_list" in entity.components.keys():
            entity.components["status_list"].add_status(self.copy(), turns)

    def copy(self): #I specifically defined this method in the original code in case I need to modify it in the future
        return copy(self)

class StatList(Component):
    entity: Entity
    stat_record: List[Stat] = []
    def __init__(self, **stats:Stat) -> None:
        for name, stat in stats.items():
            stat.stat_list = self
            stat.name = name
            self.stat_record.append(stat)

    def get_stat(self, name:str) -> Optional[Stat]:
        for stat in self.stat_record:
            if name == stat.name:
                return stat

    def get_stat_name(self, stat:Stat) -> Optional[str]:
        if stat in record:
            return stat.name

class Stat:
    name:str
    base_value:int

    def __init__(self, base:int=0):
        self.base_value = base

    @property
    def entity(self) -> Entity:
        return self.stat_list.entity

    @property
    def current_value(self) -> int:
        value = self.base_value
        for mod in self.get_modifiers():
            value += mod.value
        return int(value)

    def get_modifiers(self):
        for component in self.entity.components.values():
            if hasattr(component, "mod_list"):
                for mod in component.mod_list:
                    if mod.stat == self.name:
                        yield mod

class Modifier:
    stat: str
    value: Union[int, float]

    def __init__(self, stat:str, value:Union[int, float]):
        self.stat = stat
        self.value = value

rage = StatusEffect()
rage.name = "Rage"
rage.turn_effect = lambda entity : print(f"{entity.name} is enraged")
rage.mods = [
    Modifier("atk", 5)
]

player = Entity(
    name="Player",
    stat_list=StatList(atk=Stat(5)),
    status_list=StatusList()
).spawn()
rage.apply(player, 10)

while True:
    player.update()
    player.components["stat_list"].get_stat("atk").current_value
    input()

Unfortunately, using copy() in the spawn method would result in entities created that way sharing status effects, stats, etc., which really defeats the purpose of spawning new entities

Edit 2: Modified spawn method to use copy and to copy all components, have to add guard clauses now but it works.



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source