5

I have a class in python for a figure with attributes name, health, strength, stealth, agility, weapons and money. There is a shop in the game I'm making to increase the value of any of the integer properties with a specific item. Each integer property can be increased by one of two different items with a different cost and buff strength. The issue I'm having is actually incrementing the attribute by the amount and saving the object.

Here's the code for the object:

class Figure:
    def __init__(self, stats):
        #create figure object
        self.name = stats[0]
        self.health = int(stats[1])
        self.strength = int(stats[2])
        self.stealth = int(stats[3])
        self.agility = int(stats[4])
        self.weapons = int(stats[5])
        self.money = int(stats[6])

    def show_person(self):
        #show object attributes
        print("\n\n{}\n\nHealth: {}\nStrength: {}\nStealth: {}\nCunning: {}\nWeapons: {}\nMoney: £{}".format(self.name.title(),self.health,self.strength,self.stealth,self.cunning,self.weapons, self.money))

    def set_attr(self,attr,buff):
        #increase character attribute by a variable amount
        eval("self.{} = self.{} + {}".format(attr,attr,buff))

I might put friend.set_attr("stealth",10) to increase friend's value of stealth by 10 where friend is a variable that contains one of these Figure objects but this error is thrown:

File Computer Science\python\oop game.py", line 21, in set_attr
  exec(eval("self.{} = self.{} + {}".format(attr,attr,buff)))
File "<string>", line 1
  self.agility = self.agility + 4
                 ^
SyntaxError: invalid syntax

And I can't work out why.

Adam B
  • 163
  • 1
  • 13
  • 3
    Have you considered using a dictionary with attribute names as key instead of several variables? That way you could access the values by their names `self.attribs["agility"]` instead of using `eval`. – Christian König Oct 08 '18 at 10:12
  • 1
    Why don't you just do `friend.stealth = 10`? – saud Oct 08 '18 at 10:14
  • Also see https://stackoverflow.com/questions/2627002/whats-the-pythonic-way-to-use-getters-and-setters – saud Oct 08 '18 at 10:16
  • ["The expression argument is parsed and evaluated as a Python expression"](https://docs.python.org/3/library/functions.html#eval) – Stop harming Monica Oct 08 '18 at 10:19
  • The code in the traceback doesn't match the code in the question. – Aran-Fey Oct 08 '18 at 10:20

3 Answers3

5

Assignment is a statement and cannot be used inside an eval, which accepts only expressions. You should use exec instead:

exec("self.{} = self.{} + {}".format(attr,attr,buff))

But instead of exec, it's better to use the setattr function:

setattr(self, attr, getattr(self, attr) + buff)
blhsing
  • 91,368
  • 6
  • 71
  • 106
2

Don't use exec and eval. Use getattr and setattr:

class Foo:
    def __init__(self):
        self.x = 0

    def set_attr(self, attr, buff):
        new_val = getattr(self, attr) + buff
        setattr(self, attr, new_val)

foo = Foo()
foo.set_attr('x', 10)
print(foo.x)
# 10
foo.set_attr('x', 11)
print(foo.x)
# 21

Alternatively it is possible to use vars to modify the attribute directly (which personally I tend to like less):

class Foo:
    def __init__(self):
        self.x = 0

    def set_attr(self, attr, buff):
        vars(self)[attr] += buff

foo = Foo()
foo.set_attr('x', 10)
print(foo.x)
# 10
foo.set_attr('x', 11)
print(foo.x)
# 21
DeepSpace
  • 78,697
  • 11
  • 109
  • 154
1

Just to be clear: you know you could just type

a.foo += 2

If yes, but you need the other method:

Python already has internal functions that does exactly what you're trying to achieve. The methods are called setattr and getattr. Read more about them here. For now, here's how you can use them:

class A:
   b = 3

a = A()

setattr(a, 'b', 5)
print(a.b) # 5
print(getattr(a, 'b')) # 5

setattr(a, 'b', getattr(a, 'b') + 5)
print(a.b) # 10

So you could implement a method that increments an attribute like this:

class A:
   def incr_attr(self, attr_name, amount):
      setattr(self, attr_name, getattr(self, attr_name) + amount)

Or, even more convenient:

def incr_attrs(self, **attr_map):
    for attr_name, amount in attr_map.items():
         setattr(self, attr_name, getattr(self, attr_name) + amount)

So you can type

A.incr_attr(stealth=3, money=10)
Nearoo
  • 4,454
  • 3
  • 28
  • 39