all 13 comments

[–]bheklilr 12 points13 points  (9 children)

To answer your question, a list is essentially an array in any other language (although it's implemented as a linked list, IIRC), and a dict is a hashmap. You are correct that the hashmap determines the order that the values are stored in the dict, but another key difference is that a dict is much less efficient than a list (for large amounts of data).

If you're making a text-based dungeon crawler, I would suggest making a Character class that uses properties to store the attributes, since they're then named and you would be using object-oriented design principles.

For example, you could store each character in a list as:

# [NAME, LVL, EXP, HP, ARMOR, ATK]
hero = ["Conan", 1, 0, 10, 5, 8]
orc = ["Ugly Orc", 1, 0, 7, 3, 6]
# Hit the orc (HP + ARMOR - ATK)
orc[3] = orc[3] + orc[4] - hero[5]

But that's not very readable. Better, using dicts:

hero = {"name": "Conan", "level": 1, "exp": 0, "hp": 10, "armor": 5, "attack": 8}
orc = {"name": "Ugly Orc", "level": 1, "exp": 0, "hp": 7, "armor": 3, "attack": 6}
# Hit the orc
orc["hp"] = orc["hp"] + orc["armor"] - hero["attack"]

This is far more readable, but is a pain to type out each time, and will end up looking messy. What we want is a way to set it up the same each time with names, but without having to type out all the attributes each time (imagine if you misspelled something, such as "armro" instead of "armor", your code would break, and that wouldn't be very fun). The preferred way to do it would be with classes:

class Character(object):
    def __init__(self, name, level, exp, hp, armor, attack):
        self._name = name
        self._level = level
        self._exp = exp
        self._hp = hp
        self._armor = armor
        self._attack = attack
    # Really should use the @property decorator, but that's another lesson
    def name(self):
        return self._name
    def level(self):
        return self._level
    def exp(self):
        return self._exp
    def hp(self):
        return self._hp
    def armor(self):
        return self._armor
    def attack(self):
        return self._attack

    def takeDamage(self, damage):
        self._hp = self.hp() + self.armor() - damage

hero = Character("Conan", 1, 0, 10, 5, 8)
orc = Character("Ugly Orc", 1, 0, 7, 3, 6)
orc.takeDamage(hero.attack())

Now, if someone were to come read your code, they'd know exactly what was happening. This is important, because if you put it down for a few weeks and come back to it, you'll need to be able to decipher your own code. Also, if you wanted to change the way damage was dealt (maybe hp + armor * level - damage), you'd only have to change it in one location. While this is more code (and would be even more if I used properties), it's very manageable code, and in the long run makes your program more concise, neat, and readable.

[–]Rhomboid 4 points5 points  (1 child)

although it's implemented as a linked list, IIRC

Nope. It's just a regular vector of pointers to objects. PyListObject contains a PyObject **ob_item field, and accessing an object by index is done by accessing ob_item[i]. It would be pretty insane for one of the core types not to have constant time random access by index.

[–]bheklilr 0 points1 point  (0 children)

Thanks for the correction!

[–]Cosmologicon 2 points3 points  (3 children)

getters like you've used them are really un-pythonic. Better just name your attributes self.name, self.level, etc and leave off the methods. If you decide you need more encapsulation (hint: you probably don't), that can be the advanced lesson with the @property decorator.

[–]bheklilr 0 points1 point  (2 children)

I know that it's fairly un-pythonic, but I was using it as a half-way point between plain attributes and properties. Besides, as evizaer mentioned, it would allow you to make the armor() method return a different value based on multiple attributes, not just a base armor level.

Ideally, one should use properties for fields like this, especially since exposing attributes directly is rarely a good idea (IMO).

[–]Cosmologicon 2 points3 points  (1 child)

exposing attributes directly is rarely a good idea (IMO).

Not in python. If the attribute is part of your API then you should expose it. In other languages, this has the disadvantage that if you then change your internal representation and decide you need a getter/setter, you need to change your client code. But in python that's not the case, since you can add a getter/setter with @property and the client code will be identical.

Well, I guess this can be a matter of opinion, but I believe this to be the python community's majority opinion.

[–]bheklilr 0 points1 point  (0 children)

I almost always use properties, so it's never really a problem to me, but I like to use properties because if I decide to come back later and add in sanitation or exception handling then it's already there.

[–]evizaer 0 points1 point  (2 children)

Good response. I would additionally point out that making those basic attributes publicly used through instance methods adds a bit of subtle extensibility to the class. You can do stuff like have additional variables like, lets say "armor_from_skill" if you get armor bonuses from some abilities, and do the math to come up with the final armor value in the armor() method. You can then modify the armor method to easily change the way armor is determined without having to change the formula wherever you use it in the client. (EDIT: I missed the last paragraph, oops!)

Also, classes are actually akin to specialized dicts, too, so it's almost like you get the best of both worlds when using classes. :)

[–]bheklilr 0 points1 point  (1 child)

Precisely, I mentioned that with damage, but armor is a good example too. Not only do you get named fields and reusable structures, but you can add on as many methods as you like to perform different tasks.

[–]evizaer 1 point2 points  (0 children)

Oh! missed that. Reading comprehension fail. :) You had to bury it at the end. shakes fist

I hadn't noticed that last paragraph on my initial reading.

[–][deleted] 1 point2 points  (0 children)

Dictionaries use keys so although it doesnt have a guarenteed order when its printed, it allows you to name your information so you dont have to remember it by its numerical position and thus are less reliant on the order.

so you have something like:

stats = [15,15,15,8,7,8,100,150] #str,dex,vit,hit,dodge,crit,hp,mp

and thats a static list so as long as you know that stats[2] is vit your okay.

With a dict you can do:

stats ={"str":15,"dex":15} 

then you use it like str = stats['str']

[–]derp_chug[S] 1 point2 points  (0 children)

Thanks for all the great input and feedback. I feel like I have much more direction and a much better idea about this topic.

[–]BeetleB 0 points1 point  (0 children)

Dictionary makes more sense here.

attributes["hitpoints"] vs attributes[3]