• Advertisement
Sign in to follow this  

Which approach would be best for checking if an inventory is full in Python?

Recommended Posts

Here are 3 versions of a method from my inventory class that adds an item to the given inventory (or increases the items quantity). From a Python perspective, which approach would be best? Each method is followed by a sample of how I would use it in practice. I've been pretty interested in code design/architecture lately and I've been trying to get better at Python, so it may seem like a silly question but I'm just curious what you people think. Currently, I'm using approach #1, but I find it misleading that you could add an item and not actually have an item added. And if there's another approach not listed here that would be good please let me know.

#1 prevent adding item if is_full():
def add_item(self, item):
        i = next((i for i in self.items if i.tag == item.tag), None) #Duplicate item...
        if i is not None:
            i.quantity += item.quantity
        elif not self.is_full():
            self.items.append(item)

player_inventory.add_item(item)

#2 throw exception if is_full() and handle outside method:
def add_item(self, item):
        i = next((i for i in self.items if i.tag == item.tag), None)
        if i is not None:
            i.quantity += item.quantity
        elif not self.is_full():
            self.items.append(item)
        else: 
            raise InvetoryFullException("Inventory can only hold 8 items.")

try:			
	player_inventory.add_item(item)
except InvetoryFullException:
	pass
			
#3 check if inventory is_full() outside of method:
def add_item(self, item):
        i = next((i for i in self.items if i.tag == item.tag), None)
        if i is not None:
            i.quantity += item.quantity
        else:
            self.items.append(item)

if not player_inventory.is_full():
	player_inventory.items.append(item)

 

Edited by mrpeed

Share this post


Link to post
Share on other sites
Advertisement
9 hours ago, fastcall22 said:

items = dict()

Makes sense to use a dict actually, not sure why I went with a list.

9 hours ago, fastcall22 said:

@property def full(self): return len(self.items) >= self.max_inventory_size

Curious to why you decided to make this a property?

 

Also, what's your take on using an exception? As in my second example.

Share this post


Link to post
Share on other sites

Silently ignoring a request is a recipe for making buggy code, so I would strongly suggest you don't do that. Python also recommends that in the "Zen of Python" (type "import this" after the ">>>" prompt). "Explicit is better than implicit."

Making it testable before-hand, or returning "False" if it fails is a simple solution here. For something as simple as a full inventory, it's likely also enough. If you cannot store the item, something else must be done with it, and likely the best place to handle that problem is at the same spot in the code as where you tried (and failed) to insert the item into the inventory. An obvious thing to do is to put the item back where the player found it, for example.

An exception is a stronger failure, it is typically used when you're in real trouble. A file you loaded last time suddenly disappeared for example. The "return False" trick is a valid solution here too, but one level higher, there isn't much you can do about a missing file, so the only thing you can do is again "return False" to the next higher level, and so on, until you are somewhere near the top-level, where you know what files exist, you scratch the missing file, and you pick a new file to load.

When you raise an exception when you find the file missing instead, it does the above in one big step, it repeatedly exits the function, checks for a "catch", and if it doesn't exist, immediately exits that function too, etc, until it finds a "catch" statement that matches, or it reaches the main program (at which point the entire program ends). As such the raise is a substitute for the whole chain of
 

result = subfunction()
if not result: return False

that you'd have to write otherwise. It's a sort-of super-return.

Exceptions are quite controversial, there are disadvantages to them as well. Since you make big jumps from some innner-inner function back to some much higher function, all the variables that you have created and computations you have done in the functions between "raise" and "catch" are lost. You may not want that.

 

 

Share this post


Link to post
Share on other sites
On 14/07/2017 at 5:38 AM, fastcall22 said:

Since the items are referred to by their tag, then it would be easier to use an associative array, such as `dict` or `collections.OrderedDict`.  Then, checking if the inventory is full is only a matter of checking the number of tags it has:


items = dict()

def is_full(self):
    return len(self.items) >= 8
    
...etc...

 

Slight amendment here; I would expect that to be more like:

def __init__():
    self.items = dict()
    
... other methods here, etc ...

 

Apart from that, I completely agree with this approach, and that the ability to add to the inventory should be checked explicitly with a function rather than handled with an exception. Exceptions are best saved for situations where it's not practical to easily check for the problem or to easily handle the problem where it's discovered.

Share this post


Link to post
Share on other sites
On 7/15/2017 at 6:26 AM, Kylotan said:

Slight amendment here; I would expect that to be more like:


def __init__():
    self.items = dict()
    
... other methods here, etc ...

 

Apart from that, I completely agree with this approach, and that the ability to add to the inventory should be checked explicitly with a function rather than handled with an exception. Exceptions are best saved for situations where it's not practical to easily check for the problem or to easily handle the problem where it's discovered.

Yes, I am initializing that in the __init__ method. And makes sense, I decided to remove the is_full() check from each method and have the inventory size enforced outside of the inventory class. This also removed a lot of clutter from my methods which is good.

I was wondering if you can clarify when to use properties? I tend to avoid them because I feel like they are misleading.

For example,

@property
    def full(self):
        return len(self.items) >= self.max_inventory_size

Doesn't this hide the fact that this is a method? Would a user not think this a regular variable and possibly try to assign to it? I have other methods labeled get and set throughout my code base that changes behavior within different classes. Would it be misleading to convert those to properties?

Another example, would it be to misleading to make this a next_sprite property?

def get_next_sprite(self):
        if self.current_count == self.max_count: #Time to switch sprites...
            self.current_count = 0
            self.current_sprite = next(self.sprite_reel)
        else: #Increment and return the same sprite from last frame...
            self.current_count += 1
        return self.current_sprite

 

Edited by mrpeed

Share this post


Link to post
Share on other sites

Properties are intended to hide the fact that they are a method; whether you think that is a good idea or not is up to you. A reasonable middle-ground would be to use a property only where the hidden side-effects make sense in some way. I write entire Python programs without ever defining properties and I'm happy with that. And I see some popular Python packages that use properties in a way I don't agree with, but the world doesn't end. It's up to you.

Examples of 'good' candidates for properties, in my opinion, might be methods that fix up the incoming value on a set-property (e.g. changing a string to a unicode, or an integer to a float), or methods which wrap a more complex comparison (e.g. the 'is_full' concept)

And an example of a 'bad' candidate for properties might be methods that look like getters but which change the object state, e.g. by iterating over some internal collection. (So personally, I wouldn't like a 'next_sprite' property.)

I wouldn't worry about users wrongly thinking they can assign to a property that is read-only; Python code needs to be well-documented to be usable anyway due to the lack of type-checking, so you just need to note such properties accordingly.

Share this post


Link to post
Share on other sites
On 7/17/2017 at 5:13 AM, Kylotan said:

Examples of 'good' candidates for properties, in my opinion, might be methods that fix up the incoming value on a set-property (e.g. changing a string to a unicode, or an integer to a float), or methods which wrap a more complex comparison (e.g. the 'is_full' concept)

If I see no reason for something to be a property over a method should I favor making it a property or a method? For example, I can see no benefit in doing a "full" property over a "is_full() method", other than not having to write (). I can understand if full started as a regular variable and was expanded to a property later, but it wasn't. So in that case what would you say?

Share this post


Link to post
Share on other sites

If you don't want to use properties, then don't. They just exist for convenience, to allow you to create things that act much like regular variables but which require a little extra logic.

Share this post


Link to post
Share on other sites

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  

  • Advertisement
  • Advertisement
  • Popular Tags

  • Advertisement
  • Popular Now

  • Similar Content

    • By Sean Meredith
      Hi all, I am starting to develop a tactics game and ran into a problem I had not thought of. I began by drawing a screen with a hex grid, and this is no big deal. I got that working fine. But, I realized it didn't look quite right. This is because in most strategy games, you're not looking straight down. There is a bit of a tilt. Attached is an example of what I mean. The hexagons on bottom are larger than the hexagons on top, and I'm unsure of how to go about adding this effect. Especially when you consider that some maps may be of different sizes. 
      I'm not sure if this is the right place to post something like this, but it seems as though some sort of linear transformation would be applied? No? I don't even know where to begin in a problem like this.
      Thanks.

    • By steve negrin
      hello, i'm steve and im designer, i can design any idea and i'm looking for projects to help, if you are interesting write and we can talk about it
    • By LifeArtist
      Good Evening,
      I want to make a 2D game which involves displaying some debug information. Especially for collision, enemy sights and so on ...
      First of I was thinking about all those shapes which I need will need for debugging purposes: circles, rectangles, lines, polygons.
      I am really stucked right now because of the fundamental question:
      Where do I store my vertices positions for each line (object)? Currently I am not using a model matrix because I am using orthographic projection and set the final position within the VBO. That means that if I add a new line I would have to expand the "points" array and re-upload (recall glBufferData) it every time. The other method would be to use a model matrix and a fixed vbo for a line but it would be also messy to exactly create a line from (0,0) to (100,20) calculating the rotation and scale to make it fit.
      If I proceed with option 1 "updating the array each frame" I was thinking of having 4 draw calls every frame for the lines vao, polygons vao and so on. 
      In addition to that I am planning to use some sort of ECS based architecture. So the other question would be:
      Should I treat those debug objects as entities/components?
      For me it would make sense to treat them as entities but that's creates a new issue with the previous array approach because it would have for example a transform and render component. A special render component for debug objects (no texture etc) ... For me the transform component is also just a matrix but how would I then define a line?
      Treating them as components would'nt be a good idea in my eyes because then I would always need an entity. Well entity is just an id !? So maybe its a component?
      Regards,
      LifeArtist
  • Advertisement