-1

I have the following class:

class Resources:
    """
    Resources class.
    """

    def __init__(self, food: int, gold: int):
        self.F = food
        self.G = gold

    def __sub__(self, other):
        return Resources(self.F - other.F, self.G - other.G)

    def __getitem__(self, attr):
        return self.__getattribute__(attr)
 

I am coding another class for buying / selling specific resources. The part Im struggling with is this:

a = Resources(100, 200)
a['F'] -= 50

When executed, TypeError: 'Resources' object does not support item assignment

I can get any attribute once I know its name, but I don't know how to change its value through the -= operator.

So, to clarify, the question is: How to substract a float value to a given attribute selected by using a string, and store the result in the same attribute?

AlanWik
  • 326
  • 1
  • 10
  • 2
    You need to implement `__setitem__` – khelwood Nov 03 '20 at 21:03
  • I tried, by ```__setitem__``` requires the value to set as a parameter. How to pass to ```__setitem__``` the current value of the attribute minus the value to substract? – AlanWik Nov 03 '20 at 21:05
  • Are you trying to subtract 50 from object `a`'s `F` attribute? You can't access attributes via indexing, you have to use `a.F` (or `getattr(a, "F")`). – Adam Kern Nov 03 '20 at 21:05
  • @AdamKern, I am doing that because I want to select the attribute to modify as a string parameter in a function. – AlanWik Nov 03 '20 at 21:08
  • @AlanWik ah I'm sorry I didn't see the `__getitem__` override, although generally speaking I'm not sure that overriding `__getitem__` to perform dynamic attribute access is best practices, since that functionality is built in to Python via the `getattr` function. – Adam Kern Nov 03 '20 at 21:12

3 Answers3

0

Either do

self.resources[what] = self.resources[what] - amount

or implement the __isub__() method in your Resources class, which is called when the -= operator is used (just like the - operator calls the __sub__() method).

# mutating this object
def __isub__(self, other):
    self.F -= other.F
    self.G -= other.G
    return self

# not mutating this object, returning a copy that will be assigned over the original
def __isub__(self, other):
    return self - other  # delegate to __sub__()

(a similar method, __rsub__(), exists for if your object is used on the right side of a - sign instead of the left side. Note that this trio of methods exists for most operators).


Be careful to check whether your Resources objects should be mutable or immutable. The procedure for x -= y unrolls to x = x.__isub__(y), so the method can be built without mutating the original x, as explained by this answer.

Green Cloak Guy
  • 23,793
  • 4
  • 33
  • 53
  • [this answer addresses the title of the question and its pre-edit contents. The post-edit contents have a different error] – Green Cloak Guy Nov 03 '20 at 21:10
  • But that forces me to create another Resources instance only to store the value I want to sell, which is not a bad idea after all. But, what I would like to achieve is to substract a float value to a given attribute of an object selected with a string. – AlanWik Nov 03 '20 at 21:11
  • @AlanWik if that's the case, I think the easiest way to do it would be (given the attribute name `attr_name`) to just say `setattr(a, attr_name, getattr(a, attr_name) - 50)`. There's no need to override `__sub__`, since that seems to imply that you're subtracting from *the entire object*, not just a single attribute – Adam Kern Nov 03 '20 at 21:15
0

An answer to the post-edit question:

def reduce_resource(self, resource_name, value):
    setattr(self, resource_name, getattr(resource_name) - value)
Adam Kern
  • 566
  • 4
  • 14
0

Well, after some research I found this solution:

name = 'G'
amount = 50

a = Resources(100, 200)
vars(self.resources)[name] -= amount
AlanWik
  • 326
  • 1
  • 10