Python - Class attributes update - python

I need help with updating my __init__ attributes, I need to dynamically update them. So farm my __init__ looks like this:
class Thief(Hero):
def __init__(self, chance_to_steal=0.3, chance_to_crit=0.3):
super().__init__(hero_name="Thief", stamina=100)
self.chance_to_steal = chance_to_steal
self.chance_to_crit = chance_to_crit
self.armor = 0
self.backpack = Backpack()
I would like to update this values as the program goes forward
The function i am trying to use looks like this:
def add_item(self, item_name, item_type, modifier_amount):
self.backpack.add_new_item(item_name, item_type, modifier_amount)
if item_name in ["Armor", "Weapon"]:
value, statistic = modifier_amount.split(" ")
statistic_dict = {"Armor": self.armor, "Weapon": self.attack_dmg}
plus_minus = {"+": True, "-": False}
operator = value[0]
if plus_minus[operator]:
statistic_dict[item_name] += int(value[1:])
if not plus_minus[operator]:
statistic_dict[item_name] -= int(value[1:])
is there any way that i can modify self attributes while using dict like that?
statistic_dict = {"Armor": self.armor, "Weapon": self.attack_dmg}
At the moment dict values are the values of that attributes but i would like to modify them without having to hard code it with lots of if's
Thanks for help

I am not sure if I got exactly what you want, but maybe you are just missing the getattr and setattr callables.
They allow you to change an instance's attribute given the attribute name as a string. As in setattr(self, "armor", statistic_dict["Armor"]), or, as will be useful here: setattr(self, item_name, statistic_dict["Armor"]) (the attribute name is now used from the variable item_name and don't need to be hardcoded)
(Both callables are part of Python builtins and you don't need to import them from anywhere, just use them - also, besides getting and setting, you can use hasattr and delattr)
Besides that you have some issues with the way you stract your operator and value contents and use then - if one passes -5 in a string, say inside a amount variable, just doing my_value += int(amount) works - no need to manually check the "operator" as both "+" and "-" are valid prefixes when converting integer numbers in strings to ints.

Your main problem seems to be lack of comfort with Boolean expressions:
plus_minus = {"+": True, "-": False}
operator = value[0]
if plus_minus[operator]:
statistic_dict[item_name] += int(value[1:])
if not plus_minus[operator]:
statistic_dict[item_name] -= int(value[1:])
Instead:
if value[0] == '+':
statistic_dict[item_name] += int(value[1])
else:
statistic_dict[item_name] -= int(value[1])
Or, using a ternary operator (conditional expression):
bonus = int(value[1])
statistic_dict[item_name] += bonus if value[0] == '+' else -bonus
This updates the stat by either +bonus or -bonus, depending on the operator.
Note that this modifies only the values in your dict; it does not modify the instance attributes (not class attributes). If you want to modify those, instead, I recommend that you move the applicable attributes to a dict as part of your __init__ function, and then operate on that self.capabilities dict as the target of your add_item method.

Here's something that should get you a bit further.
Item is its own class and can have several modifiers, in a dictionary. If a modifier's key matches an existing attribute on the Hero, then it gets added to the Hero's.
As you go further, you may want to have you Modifier values be a class in its own right. A Vorpal may have an absolute 70% chance to crit, regardless of who is using it. Or a ring of thieving may multiply your base chance_to_steal by 1.3 (i.e .30 * 1.3 = .39 now).
A separate statistics dict could allow you to separate out the current values from the hero's underlying attributes so that you could take away items.
Never mind what I did to your poor Hero and Backpack classes, they don't matter for this problem, so I basically commented them out.
# class Thief(Hero):
class Thief:
def __repr__(self):
return f"{self.__class__.__name__}.chance_to_steal={self.chance_to_steal}, chance_to_crit={self.chance_to_crit}"
def __init__(self, chance_to_steal=0.3, chance_to_crit=0.3):
# super().__init__(hero_name="Thief", stamina=100)
self.chance_to_steal = chance_to_steal
self.chance_to_crit = chance_to_crit
self.armor = 0
self.backpack = {}
def add_item(self, item):
self.backpack[item.name] = item
for attrname,mod in item.modifiers.items():
#do we have something to modify, on this Hero?
val = getattr(self,attrname, None)
if val is None:
continue
else:
#sum the previous value and the modifier
setattr(self, attrname, val + mod)
class Item:
def __init__(self, name, modifiers=None):
self.name = name
if modifiers:
self.modifiers = modifiers.copy()
thief = Thief(chance_to_crit=0.4)
print(thief)
item = Item("vorpal", dict(chance_to_crit=0.2))
thief.add_item(item)
print(thief)
item = Item("cursed ring", dict(chance_to_crit=-0.1, chance_to_steal=-0.2, cast_spell=-0.2))
thief.add_item(item)
print(thief)
output
Thief.chance_to_steal=0.3, chance_to_crit=0.4
Thief.chance_to_steal=0.3, chance_to_crit=0.6000000000000001
Thief.chance_to_steal=0.09999999999999998, chance_to_crit=0.5000000000000001

Related

How to print actual name of variable class type in function?

I'm trying to return variable name, but i keep getting this:
<classes.man.man object at (some numbers (as example:0x03BDCA50))>
Below is my code:
from classes.man import man
def competition(guy1, guy2, counter1=0, counter2=0):
.......................
some *ok* manipulations
.......................
if counter1>counter2:
return guy1
bob = man(172, 'green')
bib = man(190, 'brown')
print(competition(bob , bib ))
Epilogue
If anyone want to, explain please what I can write instead of __class__ in example below to get variable name.
def __repr__(self):
return self.__class__.__name__
Anyway, thank you for all of your support
There are different ways to approach your problem.
The simplest I can fathom is if you can change the class man, make it accept an optional name in its __init__ and store it in the instance. This should look like this:
class man:
def __init__(number, color, name="John Doe"):
self.name = name
# rest of your code here
That way in your function you could just do with:
return guy1.name
Additionnally, if you want to go an extra step, you could define a __str__ method in your class man so that when you pass it to str() or print(), it shows the name instead:
# Inside class man
def __str__(self):
return self.name
That way your function could just do:
return guy1
And when you print the return value of your function it actually prints the name.
If you cannot alter class man, here is an extremely convoluted and costly suggestion, that could probably break depending on context:
import inspect
def competition(guy1, guy2, counter1=0, counter2=0):
guy1_name = ""
guy2_name = ""
for name, value in inspect.stack()[-1].frame.f_locals.items():
if value is guy1:
guy1_name = name
elif value is guy2:
guy2_name = name
if counter1 > counter2:
return guy1_name
elif counter2 > counter2:
return guy1_name
else:
return "Noone"
Valentin's answer - the first part of it at least (adding a name attribute to man) - is of course the proper, obvious solution.
Now wrt/ the second part (the inspect.stack hack), it's brittle at best - the "variables names" we're interested in might not necessarily be defined in the first parent frame, and FWIW they could as well just come from a dict etc...
Also, it's definitly not the competition() function's responsability to care about this (don't mix domain layer with presentation layer, thanks), and it's totally useless since the caller code can easily solve this part by itself:
def competition(guy1, guy2, counter1=0, counter2=0):
.......................
some *ok* manipulations
.......................
if counter1>counter2:
return guy1
def main():
bob = man(172, 'green')
bib = man(190, 'brown')
winner = competition(bob, bib)
if winner is bob:
print("bob wins")
elif winner is bib:
print("bib wins")
else:
print("tie!")
Python prints the location of class objects in memory if they are passed to the print() function as default. If you want a prettier output for a class you need to define the __repr__(self) function for that class which should return a string that is printed if an object is passed to print(). Then you can just return guy1
__repr__ is the method that defines the name in your case.
By default it gives you the object type information. If you want to print more apt name then you should override the __repr__ method
Check below code for instance
class class_with_overrided_repr:
def __repr__(self):
return "class_with_overrided_repr"
class class_without_overrided_repr:
pass
x = class_with_overrided_repr()
print x # class_with_overrided_repr
x = class_without_overrided_repr()
print x # <__main__.class_without_overrided_repr instance at 0x7f06002aa368>
Let me know if this what you want?

Is there a way to fix Name Error due to scope?

I have a function that creates a player object but when referencing the object, I get a NameError. I think it is happening due to local scope but global should fix it...
I just started out OOP and this code is working in the python shell but it is not working in script mode.
endl = lambda a: print("\n"*a)
class Score:
_tie = 0
def __init__(self):
self._name = ""
self._wins = 0
self._loses = 0
def get_name(self):
print
self._name = input().upper()
def inc_score(self, wlt):
if wlt=="w": self._wins += 1
elif wlt=="l": self._loses += 1
elif wlt=="t": _tie += 1
else: raise ValueError("Bad Input")
def player_num(): #Gets number of players
while True:
clear()
endl(10)
print("1 player or 2 players?")
endl(5)
pnum = input('Enter 1 or 2: '.rjust(55))
try:
assert int(pnum) == 1 or int(pnum) == 2
clear()
return int(pnum)
except:
print("\n\nPlease enter 1 or 2.")
def create_player(): #Creates players
global p1
p1 = Score()
yield 0 #stops here if there is only 1 player
global p2
p2 = Score()
def pr_(): #testing object
input(p1._wins)
input(p2._wins)
for i in range(player_num()):
create_player()
input(p1)
input(p1._wins())
pr_()
wherever I reference p1 I should get the required object attributes but I'm getting this error
Traceback (most recent call last):
File "G:/Python/TicTacTwo.py", line 83, in <module>
input(p1)
NameError: name 'p1' is not defined
Your issue is not with global but with the yield in create_player(), which turns the function into a generator.
What you could do:
Actually run through the generator, by executing list(create_player()) (not nice, but works).
But I suggest you re-design your code instead, e.g. by calling the method with the number of players:
def create_player(num): #Creates players
if num >= 1:
global p1
p1 = Score()
if num >= 2:
global p2
p2 = Score()
If you fix this issue, the next issues will be
1) input(p1) will print the string representation of p1 and the input will be lost, you probably want p1.get_name() instead.
2) input(p1._wins()) will raise TypeError: 'int' object is not callable
I will redesign the app to introduce really powerful python constructs that may help you when getting into OOP.
Your objects are going to represent players, then don't call them Score, call them Player.
Using _tie like that makes it a class variable, so the value is shared for all the players. With only two participants this may be true but this will come to hurt you when you try to extend to more players. Keep it as a instance variable.
I am a fan of __slots__. It is a class special variable that tells the instance variables what attributes they can have. This will prevent to insert new attributes by mistake and also improve the memory needed for each instance, you can remove this line and it will work but I suggest you leave it. __slots__ is any kind of iterable. Using tuples as they are inmutable is my recomendation.
Properties are also a really nice feature. They will act as instance attribute but allow you to specify how they behave when you get the value (a = instance.property), assign them a value (instance.property = value), or delete the value (del instance.property). Name seems to be a really nice fit for a property. The getter will just return the value stored in _name, the setter will remove the leading and trailing spaces and will capitalize the first letter of each word, and the deletter will set the default name again.
Using a single function to compute a result is not very descriptive. Let's do it with 3 functions.
The code could look like this:
# DEFAULT_NAME is a contant so that we only have to modify it here if we want another
# default name instead of having to change it in several places
DEFAULT_NAME = "Unknown"
class Player:
# ( and ) are not needed but I'll keep them for clarity
__slots__ = ("_name", "_wins", "_loses", "_ties")
# We give a default name in case none is provided when the instance is built
def __init__(self, name=DEFAULT_NAME):
self._name = name
self._wins = 0
self._loses = 0
self._ties = 0
# This is part of the name property, more specifically the getter and the documentation
#property
def name(self):
""" The name of the player """
return self._name
# This is the setter of the name property, it removes spaces with .strip() and
# capitalizes first letters of each word with .title()
#name.setter
def name(self, name):
self._name = name.strip().title()
# This is the last part, the deleter, that assigns the default name again
#name.deleter
def name(self):
self._name = DEFAULT_NAME
def won(self):
self._wins += 1
def lost(self):
self._loses += 1
def tied(self):
self._ties += 1
Now that's all we need for the player itself. The game should have a different class where the players are created.
class Game:
_min_players = 1
_max_players = 2
def __init__(self, players):
# Check that the number of players is correct
if not(self._min_players <= players <= self._max_players):
raise ValueError("Number of players is invalid")
self._players = []
for i in range(1, players+1):
self._players.append(Player(input("Insert player {}'s name: ".format(i))))
#property
def players(self):
# We return a copy of the list to avoid mutating the inner list
return self._players.copy()
Now the game would be created as follows:
def new_game():
return Game(int(input("How many players? ")))
After that you would create new methods for the game like playing matches that will call the players won, lost or tied method, etc.
I hope that some of the concepts introduced here are useful for you, like properties, slots, delegating object creation to the owner object, etc.

Set a nested object property from a dot notation string in python

I’m writing a function that takes in a parent object data and a string inputString that may or may not include dot notation to represent nested objects (i.e. ‘nestedObject.itemA). The function should set the inputString attribute of data to a random string. If the string inputString is a nested object, the function should set the nested object’s value to be a random string. I can’t figure out how to handle this all in a for-loop. I want to do something like this:
split_objects = value.split(“.”)
for item in split_objects:
data.__setattr__(item, get_random_string())
However, in the case of nested objects, the above would set the nested object to be a random string, instead of the field inside. Would someone be able to help me with the syntax to handle both cases? Thanks in advance…
You need to get a reference to data.nestedObject before you can use setattr to change data.nestedObject.itemA.
prefix, suffix = value.rsplit(".",1)
# now prefix is nestedOjbect and suffix is itemA
ref = getattr(data,prefix)
setattr(ref,suffix,get_random_string())
You need to get the reference as many times as there are dots in inputString. So, if you have an arbitrarily deeply nested structure in data
value = "nestedObject.nestedObject2.nestedObject3.itemA"
path, attribute = value.rsplit(".",1)
path = path.split(".")
ref = data
while path:
element, path = path[0], path[1:]
ref = getattr(ref, element)
setattr(ref, attribute, get_random_string())
Here is some example code I to demo a "setField" function I wrote that similar to what you are looking for:
def setField(obj, fieldPath, value):
fields = fieldPath.split(".")
cur = obj
# use all but the last field to traverse the objects
for field in fields[:-1]:
cur = getattr(cur, field)
# use the last field as the property within the object to be overwritten (not traversed)
setattr(cur, fields[-1], value)
# USE CASE EXAMPLE:
class PrintBase:
def dump(self, level=0):
for key, value in vars(self).iteritems():
print " "*(level*4) + key + ":", value
if isinstance(value, PrintBase):
value.dump(level+1)
class BottomObject(PrintBase):
def __init__(self):
self.fieldZ = 'bottomX'
class MiddleObject(PrintBase):
def __init__(self):
self.fieldX = 'middleQ'
self.fieldY = BottomObject()
class TopObject(PrintBase):
def __init__(self):
self.fieldA = 'topA'
self.fieldB = MiddleObject()
top_obj = TopObject()
print "=== BEFORE ==="
top_obj.dump()
print "=== AFTER ==="
setField(top_obj, 'fieldB.fieldY.fieldZ', '!!!! test value !!!!')
top_obj.dump()
And here is the example output:
=== BEFORE ===
fieldB: <__main__.MiddleObject instance at 0x7f5eb1cc6b48>
fieldX: middleQ
fieldY: <__main__.BottomObject instance at 0x7f5eb1cc6b90>
fieldZ: bottomX
fieldA: topA
=== AFTER ===
fieldB: <__main__.MiddleObject instance at 0x7f5eb1cc6b48>
fieldX: middleQ
fieldY: <__main__.BottomObject instance at 0x7f5eb1cc6b90>
fieldZ: !!!! test value !!!!
fieldA: topA

dynamic instances of a class object overwriting each other

I have a simple class that stores simple data. The class is as follows.
class DataFormater:
def __init__(self, N, P, K, price):
self.N = N
self.P = P
self.K = K
self.price = price
The code that calls this class is
from DataFormater import DataFormater
#global variables
ObjectList = [0,1,2,3,4,5,6,7,8,9,10,
11,12,13,14,15,16,17,18,19,20,
21,22,23,24,25,26,27,28,29,30,
31,32,33,34,35,36,37,38,39,40,
41,42,43,44,45,46,47,48,49,50]
ObjectListCounter = 0
# main
print "enter you N-P-K values, followed by a coma, then the price"
print "example ----> 5 5 5 %50 "
print "return as many values as you want to sort, then enter, 'done!' when done."
while True:
RawData = raw_input()
if RawData == 'done!':
break
else:
ObjectList[ObjectListCounter] = DataFormater
ObjectList[ObjectListCounter].N = int(RawData[0])
# very simple test way of putting first indice in ObjectList[ObjectListCounter].N
ObjectListCounter += 1
print ObjectList[0].N
print ObjectList[1].N
My idea is that ObjectList[0] would create that object '1' that I could call with 1.N
But, when I call these, it seems that I have overwritten the previous instances.
this is what prints...
return as many values as you want to sort, then enter, 'done!' when done.
12
1
done!
1
1
Thanks so much! And I know that my post is messy, I don't exactly know how to make it more "pretty"
So, it looks like you are assigning the actual class (instead of an instance of the class) in your loop. Where you do this:
ObjectList[ObjectListCounter] = DataFormater
I think what you actually want is this
ObjectList[ObjectListCounter] = DataFormater(...insert args here....)
EDIT to address the comments:
Your class init method looks like this:
def __init__(self, N, P, K, price):
That means that to create an instance of your class, it would look like this:
my_formater = DataFormater(1, 2, 3, 4)
You would then be able to access my_formater.N which would have a value of 1.
What you are trying to do instead is access a CLASS level attribute, DataFormater.N. This is generally used in situations where you have a constant variable that does not change between instances of the class. For example:
class DataFormater():
CONSTANT_THING = 'my thing that is always the same for every instance'
You would then be able to access that variable directly from the class, like this:
DataFormater.CONSTANT_THING
I hope that clears things up.

How to dynamically assign values to class properties in Python?

I'm trying to do the following:
After performing a regex group search, I'm trying to assign the results to the class properties by a specific order. the number of results from the regex search varies from 1-5 values.
class Classification():
def __init__(self, Entry):
self.Entry = Entry
self.Section = ''
self.Class = 'Null'
self.Subclass = 'Null'
self.Group = 'Null'
self.Subgroup = 'Null'
def ParseSymbol(self,regex):
Properties_Pointers = [self.Section,self.Class,self.Subclass,self.Group,self.Subgroup]
Pattern_groups = re.search(regex, self.Symbol)
i = 0
for group in Pattern_groups.groups():
Properties_Pointers[i] = group
i += 1
the problem is that for each loop iteration, instead of the class property, Properties_Pointers[i] gets the property's value (and of course in this case I can't assign the desired value to the property).
thanks.
Refer to attribute names instead, and use the setattr() function to store a new value on self:
def ParseSymbol(self, regex):
attributes = ['Section', 'Class', 'Subclass', 'Group', 'Subgroup']
Pattern_groups = re.search(regex, self.Symbol)
for group, attr in zip(Pattern_groups.groups(), attributes):
setattr(self, attr, group)
setattr() lets you set attributes based on a variable, here taking from attributes; there is also a companion getattr() function to retrieve attributes dynamically.
setattr() will set the attributes of an object based on a string name. You can rewrite ParseSymbol above:
def ParseSymbol(self,regex):
Properties_Pointers = ['Section','Class','Subclass','Group','Subgroup']
Pattern_groups = re.search(regex, self.Symbol)
i = 0
for group in Pattern_groups.groups():
setattr(self, Properties_Pointers[i], group)
i += 1
As a side note, you can iterate over both Pattern_groups.groups() and Pattern_Pointers simultaneously by using zip(). This cleans up the code by removing the index variable i and its incrementation:
for pointer, group in zip(Properties_Pointers, Pattern_groups.groups()):
setattr(self, pointer, group)
If you know that your regex will always contain the same number of groups, you can just use tuple unpacking:
self.Section, self.Class, self.Subclass,self.Group, self.Subgroup = Pattern_groups.groups()

Categories