Search code examples
pythondictionaryrandompropertiespython-descriptors

How does one return a random number from a dictionary every time a key is called?


I am trying to create a weapon that provides random damage. I am doing so using an item database in the form

itemsList = {
1: {"name": "Padded Armor",  "armor": 1,  "value": 5, "class": "Light"},
2: {"name": "Leather Armor",  "armor": 2,  "value": 10, "class": "Light"},
....
19: {"name": "Dagger", "damage" : int(random.randrange(1, 4)), "value": 2, "Type": "Dagger"},
20: {"name": "Dagger + 1", "damage" : int(random.randrange(1, 4) + 1), "value": 200, "Type": "Dagger"},
21: {"name": "Dagger + 2", "damage" : int(random.randrange(1, 4) + 2), "value": 750, "Type": "Dagger"},
22: {"name": "Dagger + 3", "damage" : int(random.randrange(1, 4) + 3), "value": 2000, "Type": "Dagger"}
}

Every time I attempt to call "damage" it just returns the same result. I understand that this is because the random number is generated one and then saved to that dictionary key.

How would I go about generating a random number every time damage is called?


Solution

  • I would go to the trouble of making the things in itemsList instances of a class. Although you might think that's overkill, doing it will give you a lot of programming flexibility later. It'll also make a lot of the code easier to write (and read) because you'll be able to refer to things using dot notation instead of via indexing which means you'll not only be able write to now use itemsList[19].damage instead of itemsList[19]["damage"]. You'll also use the same syntax for all other attributes such as itemsList[1].name and itemsList[2].value, as well as be able to write conditional code like this:

    if hasattr(itemsList[2], 'Class'):
        # do something based on itemsList[2].Class
    

    Here's what I mean:

    import random
    
    class Gear(object):
        def __init__(self, **kwargs):
            kwargs['_damage'] = kwargs.pop('damage', False)
            self.__dict__.update(kwargs)
    
        @property
        def damage(self):
            return int(random.randrange(*self._damage)) if self._damage else 0
    
    itemsList = {
        1: Gear(name="Padded Armor", armor=1, value=5, Class="Light"),
        2: Gear(name="Leather Armor", armor=2, value=10, Class="Light"),
        # ...
        19: Gear(name="Dagger", damage=(1, 4), value=2, Type="Dagger"),
        20: Gear(name="Dagger + 1", damage=(1, 5), value=200, Type="Dagger"),
        21: Gear(name="Dagger + 2", damage=(1, 9), value=750, Type="Dagger"),
        22: Gear(name="Dagger + 3", damage=(2, 6), value=2000, Type="Dagger"),
    }
    

    To answer your follow-on questions in one comment below:

    kwargs is a dictionary of all the keyword arguments passed to the __init__() constructor method. (__dict__ is the name of a dictionary where each instance stores its attributes.) See this section in the documentation.

    damage is defined by the class using a @property decorator which is a simple way to actualy make it a "data descriptor". This sets things up so that whenever you reference a class instance's damage attribute, it will call a function to retrieve/determine its current "value". See the Descriptor HowTo Guide for more information on Python descriptors.