Set a nested object property from a dot notation string in python - 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

Related

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

how to get class instances out of a dict in python

I have a class `Collection' that looks like this:
class Collection():
def __init__(self, db, collection_name):
self.db = db
self.collection_name = collection_name
if not hasattr(self.__class__, 'client'):
self.__class__.client = MongoClient()
self.data_base = getattr(self.client, self.db)
self.collection = getattr(self.data_base, self.collection_name)
def getCollectionKeys(self):
....etc.
I cleverly created a function to create a dictionary of class instances as follows:
def getCollections():
collections_dict = {}
for i in range(len(db_collection_names)):
collections_dict[db_collection_names[i]] = Collection(database_name, db_collection_names[i])
return collections_dict
it works. however, whenever I want to access a class instance, I have to go through the dictionary:
agents_keys = collections_dict['agents'].getCollectionKeys()
I would love to just write:
agents_keys = agents.getCollectionKeys()
Is there a simple way to get those instances "out" of the dict?
You can get a reference to items in a vanilla python dictionary using a generator object in a for loop, or by using a list expression.
agent_keys = [x.getCollectionKeys() for x in collections_dict.values()]
or this
agent_keys = []
for name in db_collection_names:
#do something with individual item
#there could also be some logic here about which keys to append
agent_keys.append(collections_dict[name].getCollectionKeys())
#now agent_keys is full of all the keys
My mental model of how objects are interacted with in python. Feel free to edit if you actually know how it works.
You cannot "take" items of the dictionary per say unless you call the del operator which removes the association of a variable name (that is what you type in the editor like "foo" and "bar") with an object ( the actual collections of bits in the program your machine sees). What you can do is get a reference to the object, which in python is a symbol that for all your intents and purposes is the object you want.
The dictionary just holds a bunch of references to your database objects.
The expression collections_dict['agents'] is equivalent to your original database object that you put into the dictionary like this
collections_dict['agents'] = my_particular_object

Class returns empty list

I am trying to write a function which cleans up URLs (strips them of anything like "www.", "http://" etc.) to create a list that I can sort alphabetically.
I have tried to do this by creating a class including a method to detect the term I would like to remove from the URL-string, and remove it. The bit where I am struggling is that I want to add the modified URLs to a new list called new_strings, and then use that new list when I call the method for a second time on a different term, so that step by step I can remove all unwanted elements from the URL-string.
For some reason my current code returns an empty list, and I am also struggling to understand whether new_strings should be passed to __init__ or not? I guess I am a bit confused with global vs. local variables, and some help and explanation would be greatly appreciated. :)
Thanks! Code below.
class URL_Cleaner(object):
def __init__(self, old_strings, new_strings, term):
self.old_strings = old_strings
self.new_strings = new_strings
self.term = term
new_strings = []
def delete_term(self, new_strings):
for self.string in self.old_strings:
if self.term in string:
new_string = string.replace(term, "")
self.new_strings.append(new_string)
else:
self.new_strings.append(string)
return self.new_strings
print "\n" .join(new_strings) #for checking; will be removed later
strings = ["www.google.com", "http://www.google.com", "https://www.google.com"]
new_strings = []
www = URL_Cleaner(strings, new_strings, "www.")
Why are we making a class to do this?
for string in strings:
string.replace("www.","")
Isn't that what you're trying to accomplish?
Regardless the problem is in your class definition. Pay attention to scopes:
class URL_Cleaner(object):
def __init__(self, old_strings, new_strings, term):
"""These are all instance objects"""
self.old_strings = old_strings
self.new_strings = new_strings
self.term = term
new_strings = [] # this is a class object
def delete_term(self, new_strings):
"""You never actually call this function! It never does anything!"""
for self.string in self.old_strings:
if self.term in string:
new_string = string.replace(term, "")
self.new_strings.append(new_string)
else:
self.new_strings.append(string)
return self.new_strings
print "\n" .join(new_strings) #for checking; will be removed later
# this is referring the class object, and will be evaluated when
# the class is defined, NOT when the object is created!
I've commented your code the necessary reasons.... To fix:
class URL_Cleaner(object):
def __init__(self, old_strings):
"""Cleans URL of 'http://www.'"""
self.old_strings = old_strings
cleaned_strings = self.clean_strings()
def clean_strings(self):
"""Clean the strings"""
accumulator = []
for string in self.old_strings:
string = string.replace("http://", "").replace("www.", "")
# this might be better as string = re.sub("http://(?:www.)?", "", string)
# but I'm not going to introduce re yet.
accumulator.append(string)
return accumulator
# this whole function is just:
## return [re.sub("http://(?:www.)?", "", string, flags=re.I) for string in self.old_strings]
# but that's not as readable imo.
You just need to define new_strings as
self.new_strings = []
and remove new_strings argument from the constructor.
The 'new_strings' and 'self.new_strings' are two different lists.

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()

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