Sign in to follow this  

Inventory in Python?

This topic is 3626 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I'm currently making a text-based RPG, and I've hit a hitch in trying to implement an inventory. I understand that it's best done with a list of objects, and I've got that down, but I've encountered a problem trying to find and remove objects. Since the objects aren't assigned with identifiers (such as 'a = Item()'), they basically just exist as memory addresses. I had the hindsight to pass a "Name" variable into the class. However, Because there's no actual identifier, I can't just use list.remove(item). Is there a way to reference objects by a parameter? Edited for Clarity.

Share this post


Link to post
Share on other sites
First of all: Congratulations on choosing Python! I bet you are gonna do better than most of the people that start out with C++.

If I understood it right, and I think I did, you could just do:


for item in inventory:
if item.name == something:
inventroy.remove(item)
break








I don't know exactly what happends with the loop after an item has been deleted, but it should work if you only delete 1 item per loop I guess.
(Gonna try it and edit)

But, maybe there is a better way. How do you know which item you want to delete anyway? User types in name, or what?


EDIT: By the way, you could use a dictionary if the names are going to be distinct, that way you can just do:


del inventory["Big Sword"]




or use it to do whatever, like:

inventory["Big Sword"].equip()





And I thought I would throw in a piece of advice, so here goes...

In this case it's more efficient too (I think anyways, I've learnt not to rely on myself in these matters), BUT in general: Always ignore efficiency if you can make your code look simpler, prettier and more readable instead, and make optimizations ONLY after having noticed that you really need them.

Actually, that's also a piece of advice: Don't rely on your own guesses, especially when it comes to performance/efficiency---test things!


EDIT 2:

l = [0, 1, 2, 3, 4]

for i in l:
print i
if i == 2:
l.remove(i)




Gives the output:
0
1
2
4

So it looks like the number 3 gets to step down into the position the number 2 was in, resulting in the loop skipping it.

[Edited by - tufflax on January 7, 2008 2:24:27 AM]

Share this post


Link to post
Share on other sites
Yeah, removing objects from containers while you iterate over them is a bad idea, and it could give some unpredictable behaviour every now and then. A much better alternative would be to say:

inventory = [ item for item in inventory if item.name != "foo" ]

Since none of the items are created again, the only expensive thing about this is allocating the new list, which shouldn't be expensive at all with small (As in < 100-500 elements) lists.

However, tufflax is correct, it sounds like what you want to use is a dictionary. Let's say we got a hero who has the following inventory:

Meelee: Big Huge Sword of Doom
Range: Long Bow of Leprechauns
Chest: Armour of Pizza Boxes
...

What you'd say in Python is basically this:

inventory = {
"Meelee": big_sword,
"Range": longbow,
"Chest": armour
...
}

Now you can do things like saying: intentory["Range"] and get the ranged weapon of the hero. Or inventory["Range"] = Photon_Cannon to replace it. Since you probably want all keys preserved in the inventory list, it's probably a better idea to assign None to those keys that the Hero doesn't possess;
>>> inventory["Mouse trap"] = None
>>> print inventory
# {"Meelee": big_sword ... "Mouse trap": None ... }

Good luck! When you've got a basic control on Python you should check out pygame.

Share this post


Link to post
Share on other sites
I see... I hadn't really thought about using a dictionary for an inventory. It seems a bit weird still. My original code was:


def removeItem(self, item):
if self.hasItem(item):
self._inventory.remove(item)
def hasItem(self, item):
return item in self._inventory












As you can see, that simply won't work, when item is called "<__main__.Item object at 0xb7d2468c>". I'm not sure a dictionary would really help either, since that's not particularly useful for dynamic reasons.

For instance, suppose the player buys 3 potions and a sword. That would be represented by.

Player.addItem("Potion")
Player.addItem("Potion")
Player.addItem("Potion")
Player.addItem("Rusty Sword")


with addItem being:


def addItem(self, name):
self._inventory.append(Item(name))
self._inventory.sort()











The problem with a dictionary like that is that the list is largely dynamic. If all else fails, I can just do a more manual binary search, but I was wondering if anyone simply knew how to call objects by parameter.

Someone IRL suggested overriding __eq__, but that doesn't really seem to work.

EDIT: Also, I'm aware that the addItem example doesn't really "do" anything at the moment, but that's normal for right now, when the important thing is just getting the items "found". I suppose in the end, the shopkeeper might have something like a dictionary, and I'd take items from that to append to the inventory list.

EDIT2: Oh, that was deceptively simple. I figured it out. Apparently, Python doesn't mind you adding the same identifier to the list twice, so there's no reason not to use them. For instance:

Potion = Item("Potion", #stuff)
Sword = Item("Sword", #stuff)
Skeleton_Key = Item("Skeleton Key", #stuff)
Inventory.append(Potion)
Inventory.append(Potion)
Inventory.append(Potion)
Inventory.append(Sword)
Inventory.append(Skeleton_Key)


(Terrible casing I know, but meh, it's an example)

The entire reason I was avoiding identifiers has pretty much been nullified.

EDIT3:

Quote:
Original post by qebab
Good luck! When you've got a basic control on Python you should check out pygame.


Yeah, I used it before when trying to make a Tetris clone (one that failed miserably, that is :) ) and I'm thinking of either using that or widgets for an interface eventually. I'm not sure right now, so I'm just focusing on the model.

EDIT4 (sorry if this is getting redundant):

So, apparently, overriding __eq__ does work. It's quite handy, as now I can just do this:


class Item(object):
def __init__(self,name, ...):
self.name = name
...
def __eq__ (self, y):
return self.name == y




What this means is that

Item("Potion", ...) == "Potion"


Would return True. Well... assuming the ellipses were filled in, that is.

[Edited by - SeraphLance on January 7, 2008 10:44:49 AM]

Share this post


Link to post
Share on other sites
Quote:
Original post by SeraphLance
EDIT2: Oh, that was deceptively simple. I figured it out. Apparently, Python doesn't mind you adding the same identifier to the list twice, so there's no reason not to use them. For instance:

Potion = Item("Potion", #stuff)
Sword = Item("Sword", #stuff)
Skeleton_Key = Item("Skeleton Key", #stuff)
Inventory.append(Potion)
Inventory.append(Potion)
Inventory.append(Potion)
Inventory.append(Sword)
Inventory.append(Skeleton_Key)


That gives you 3 references to the SAME potion. I don't think that's what you intended. If you want new items each time, create them each time.

Inventory.append(Item("Potion", #stuff))


Quote:
So, apparently, overriding __eq__ does work. It's quite handy, as now I can just do this:


Yes, but it's the wrong thing to do. Your item doesn't equal a string, so don't treat it as such. Give Items some sort of type() member function that returns self.name and compare that to "potion" or whatever.

Share this post


Link to post
Share on other sites
Quote:
Original post by tufflax
So, is your problem solved or not? :P

If not, tell us what exactly you want to be able to do with you inventory.


Well, I thought it was, but after reading the responses, I'm not sure.

What I "want" to be able to do is pick out an object from a list based on it's data member.

For instance, a short version:


class ExampleItem(Object):
def __init__ (self, name):
self.name = name
list = []
list.append(ExampleItem("Potion")
list.append(ExampleItem("Potion")
list.append(ExampleItem("Potion")
list.append(ExampleItem("Skeleton Key")
list.append(ExampleItem("Skeleton Key")
list.append(ExampleItem("Talisman")


I want to be able to type something akin to:


>>>list
["Potion", "Potion", "Potion", "Skeleton Key", "Skeleton Key", "Talisman"]
>>>list.remove("Potion")
>>>list
["Potion", "Potion", "Skeleton Key", "Skeleton Key", "Talisman"]


Were this a list of strings, it would work. However, it's a list of objects, and I can't really delete memory addresses directly with any efficacy.

Quote:
Original post by Kylotan
That gives you 3 references to the SAME potion. I don't think that's what you intended. If you want new items each time, create them each time.

Inventory.append(Item("Potion", #stuff))


Well, I can see the drawback now would be that I'd have to instantiate every single item in the game, which could be cumbersome, but it would have the same practical effect, at least as far as I can see.

Quote:
Yes, but it's the wrong thing to do. Your item doesn't equal a string, so don't treat it as such. Give Items some sort of type() member function that returns self.name and compare that to "potion" or whatever.


type()? You mean... like a getName() method? That would require me to make my own search and sort method for the inventory, right?


Quote:
Original post by tufflax
By the way, what did you mean by this?
Quote:

The problem with a dictionary like that is that the list is largely dynamic.


Well, a dictionary is just a key-item correspondence, and it's not very flexible. I can't exactly do this:


invdict = {"Slot1": ExampleItem("Potion"),
"Slot2": ExampleItem("Potion"),
"Slot3": ExampleItem("Potion"),
"Slot4": ExampleItem("Skeleton Key"),
"Slot5": ExampleItem("Skeleton Key"),
"Slot6": ExampleItem("Talisman")}


...and add new slots every time I get a new item. Well, I could, but it wouldn't really solve anything, and it'd just make a lot of overhead for absolutely no reason. Maybe for an equipment issue (which was what I think you may have mistaken my problem for) but not an item inventory.

Share this post


Link to post
Share on other sites
Just a thought but how about two classes one for the inventory and an inventory control class. so then inventory.add(potion) looks for an inventory control object in a list with the name potion if it exists up the count. if not add it. remove would look up the inventory control and decrement the count and destroy if zero.

Share this post


Link to post
Share on other sites
Ok, so make an inventory class, and have add() and remove() methods for it. The remove method would work something like this:


def remove(self, item_name):
remove_list = []
for item in self._inventory_list:
if item.name == item_name:
remove_list.append(item)
for item in remove_list:
self._inventory_list.remove(item)









Or, if you just want to delete 1 item for each remove call, just delete 1 and break the loop, as in my first post.

Notice: "item.name" not item.getName(). If you don't want to do anything special when you retrieve name, just let it be public data. If you later decide that you want to do something special, or want to prohibit that the name is set, use a property and you will not have to change anything.

Quote:

Quote:
Original post by Kylotan
That gives you 3 references to the SAME potion. I don't think that's what you intended. If you want new items each time, create them each time.

Inventory.append(Item("Potion", #stuff))


Well, I can see the drawback now would be that I'd have to instantiate every single item in the game, which could be cumbersome, but it would have the same practical effect, at least as far as I can see.

What? Yes, you have to instantiate every item when you need them. Not in advance or anything like that. Actually you did that yourself earlier in that post.

Quote:

Quote:
Yes, but it's the wrong thing to do. Your item doesn't equal a string, so don't treat it as such. Give Items some sort of type() member function that returns self.name and compare that to "potion" or whatever.


type()? You mean... like a getName() method? That would require me to make my own search and sort method for the inventory, right?


Kylotan means that a string is never equal to an item, because they are not the same thing. What you should do is something like item.type == whatever (again, use properties if possible). But if you want to use strings, as it seems in your other posts, just use item.name.

As for your own search and sort, no. The sort method can sort using a given comparison function, so you will just have to provide a function that compares two items, not the whole sort. Look:


>>> help(list.sort)
Help on method_descriptor:

sort(...)
L.sort(cmp=None, key=None, reverse=False) -- stable sort *IN PLACE*;
cmp(x, y) -> -1, 0, 1


If you need more help on that, try google.

And think about it, the sorting algorithm is already there. It would be wholly redundant to make a new one. The only thing that is different is the comparison.

If your compare function is a natural way of comparing items, you can use it as __cmp__(self, other) member function too, and use sort() without any arguments. (Or maybe sort uses __lt__(self, other) (less than))

[Edited by - tufflax on January 9, 2008 9:59:49 AM]

Share this post


Link to post
Share on other sites
Quote:
Original post by Kylotan
Quote:
Original post by SeraphLance
So, apparently, overriding __eq__ does work. It's quite handy, as now I can just do this:


Yes, but it's the wrong thing to do. Your item doesn't equal a string, so don't treat it as such. Give Items some sort of type() member function that returns self.name and compare that to "potion" or whatever.


Alternatively, just inspect the .__class__.__name__ of the object.

Share this post


Link to post
Share on other sites
Wouldn't that return the name of the class (eg. "Item") rather than his name for that item instance (eg. "Potion")?

Anyway, I think the confusion seems to be arising by SeraphLance expecting a dictionary or a list to magically work with the Item objects. The original question was, "However, Because there's no actual identifier, I can't just use list.remove(item). Is there a way to reference objects by a parameter?" The answer is to find the item ((by iterating through the loop, which is easy enough, and comparing based on some value of Item), assign it to an identifier, and then call remove on that.

Share this post


Link to post
Share on other sites

This topic is 3626 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this