How to dynamically assign values to class properties in Python? - 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()

Related

Write a function to reduce duplicate code

I have two very similar for loops, I want to have an inner function to reduce the duplicate codes, they look like this:
team_members = TeamMember.objects.all()
managers = Manager.objects.all()
for m in managers:
name = f"{m.name.first_name} {m.name.last_name}"
//reset of the code are the same
for t in team_members:
name = f"{t.member.first_name} {t.member.last_name}"
//reset of the code are the same
So the problem is managers and team_members querysets have different field names for people's names.
If I want to write an inner function, how to solve the different field names?
you could pass in m.name and t.member to that function which would allow it to access that item.
for m in managers:
func(m.name)
for t in team_members:
func(t.member)
def func(member):
name = f'{member.first_name} {member.last_name}
#Rest of code
Here is the inner function which will take objs as input and fetch the values based on objects attribute.
def inner_fun(objs):
for obj in objs:
if hasattr(obj, 'name'):
name_obj = getattr(obj, 'name')
else:
name = getattr(obj, 'member')
name = f"{name_obj.first_name} {name_obj.last_name}"
return name
team_members = TeamMember.objects.all()
managers = Manager.objects.all()
team_name = inner_fun(team_members)
manager_name = inner_fun(managers)
Tom Karzes solution in code:
team_members = TeamMember.objects.all()
managers = Manager.objects.all()
for group, attr_name in zip([team_members, managers], ['name', 'member']):
for person in group:
name = f"{getattr(person, attr_name).first_name} {getattr(person, attr_name).last_name}"

Python - Class attributes update

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

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

Calling Object inside of List

class MySong:
_songTitle = "Song Title"
_artistName = "Artist Name"
_likeIndicator = -1
def setTitleAndArtist(self, songTitle, artistName):
self._songTitle = songTitle
self._artistName = artistName
def setLike(self, likeIndicator):
self._likeIndicator = likeIndicator
def undoSetLike(self, songTitle):
Null
def getTitle(self):
return self._songTitle
def getArtist(self):
return self._artistName
def getLikeIndicator(self):
return self._likeIndicator
class MyPlaylist:
_mySongs = []
def add(self, song):
self._mySongs.append(song)
def showTitles(self):
index = 0
titlesList = []
while index != len(self._mySongs):
titlesList.append(self._mySongs[index].getTitle())
index = index + 1
return titlesList
def remove(self):
remindex = 0
while remindex != len(self._mySongs):
if (self._mySongs[index].getTitle()) == remChoice :
return("Song FOUND debug!")
self._mySongs.remove(index)
else:
remindex = remindex + 1
return("Song NOT FOUND debug!")
def getMySong(self):
Null
There is a list of song objects inside of _mySongs = []. I'm trying to remove one, based on the title variable of that object.
In a separate (unshown) part of the program, the user is asked to enter the title of the song they want removed as a string. This is saved as remChoice.
I'm not entirely sure how to remove the song based on the title.
I've tried for a while to get it going, obviously we find the index of the song in the list by matching it to the title (by calling the getTitle method), then removing that index when it's found.
This isn't working. Where am I going wrong?
If you want to delete an item from a list knowing it's index use:
del xs[i]
Where i is the index. (e.g: Your song's index based on your search).
list.remove() is used for removing a matching element form the list not the "ith" item.
You might also find that a list is not a suitable data structure here? Perhaps you could try storing key/value pairs in a dict. e.g:
my_songs = {}
my_aongs["My Song Title"] = MySong(title, description, length)
You can later delete songs via their keys:
del my_songs["My Song Title"]
where titles are your keys. This saves you from doing O(n) searching.
Update:
Your .remove() method should look more like the following:
def remove(self, title):
for i, song in enumerate(self._mySongs):
if song.getTitle() == title:
del self._mySongs[i]
return
print("Song not found!")
Here we're using list's iteration protocol by using a for x in xs: rather than using a while loop and doing manual bookkeeping. The builtin function enumerate() is also used to give us an index into the list we're iterating over (i.e: it's position in the sequence).
try
self._mySongs.remove(title)
That should work.
(Or from another object: replace self by whatever your object name is)

Class design: Last Modified

My class:
class ManagementReview:
"""Class describing ManagementReview Object.
"""
# Class attributes
id = 0
Title = 'New Management Review Object'
fiscal_year = ''
region = ''
review_date = ''
date_completed = ''
prepared_by = ''
__goals = [] # List of <ManagementReviewGoals>.
__objectives = [] # List of <ManagementReviewObjetives>.
__actions = [] # List of <ManagementReviewActions>.
__deliverables = [] # List of <ManagementReviewDeliverable>.
__issues = [] # List of <ManagementReviewIssue>.
__created = ''
__created_by = ''
__modified = ''
__modified_by = ''
The __modified attribute is a datetime string in isoformat. I want that attribute to be automatically to be upated to datetime.now().isoformat() every time one of the other attributes is updated. For each of the other attributes I have a setter like:
def setObjectives(self,objectives):
mro = ManagementReviewObjective(args)
self.__objectives.append(mro)
So, is there an easier way to than to add a line like:
self.__modified = datetime.now().isoformat()
to every setter?
Thanks! :)
To update __modified when instance attributes are modified (as in your example of self.__objectives), you could override __setattr__.
For example, you could add this to your class:
def __setattr__(self, name, value):
# set the value like usual and then update the modified attribute too
self.__dict__[name] = value
self.__dict__['__modified'] = datetime.now().isoformat()
Perhaps adding a decorator before each setter?
If you have a method that commits the changes made to these attributes to a database (like a save() method or update_record() method. Something like that), you could just append the
self.__modified = datetime.now().isoformat()
just before its all committed, since thats the only time it really matters anyway.

Categories