getattr and setattr on nested subobjects / chained properties?
You could use functools.reduce
:
import functoolsdef rsetattr(obj, attr, val): pre, _, post = attr.rpartition('.') return setattr(rgetattr(obj, pre) if pre else obj, post, val)# using wonder's beautiful simplification: https://stackoverflow.com/questions/31174295/getattr-and-setattr-on-nested-objects/31174427?noredirect=1#comment86638618_31174427def rgetattr(obj, attr, *args): def _getattr(obj, attr): return getattr(obj, attr, *args) return functools.reduce(_getattr, [obj] + attr.split('.'))
rgetattr
and rsetattr
are drop-in replacements for getattr
and setattr
,which can also handle dotted attr
strings.
import functoolsclass Person(object): def __init__(self): self.pet = Pet() self.residence = Residence()class Pet(object): def __init__(self,name='Fido',species='Dog'): self.name = name self.species = speciesclass Residence(object): def __init__(self,type='House',sqft=None): self.type = type self.sqft=sqftdef rsetattr(obj, attr, val): pre, _, post = attr.rpartition('.') return setattr(rgetattr(obj, pre) if pre else obj, post, val)def rgetattr(obj, attr, *args): def _getattr(obj, attr): return getattr(obj, attr, *args) return functools.reduce(_getattr, [obj] + attr.split('.'))
if __name__=='__main__': p = Person() print(rgetattr(p, 'pet.favorite.color', 'calico')) # 'calico' try: # Without a default argument, `rgetattr`, like `getattr`, raises # AttributeError when the dotted attribute is missing print(rgetattr(p, 'pet.favorite.color')) except AttributeError as err: print(err) # 'Pet' object has no attribute 'favorite' rsetattr(p, 'pet.name', 'Sparky') rsetattr(p, 'residence.type', 'Apartment') print(p.__dict__) print(p.pet.name) # Sparky print(p.residence.type) # Apartment
For an out of the box solution, you can use operator.attrgetter
:
from operator import attrgetterattrgetter(dotted_path)(obj)
For one parent and one child:
if __name__=='__main__': p = Person() parent, child = 'pet.name'.split('.') setattr(getattr(p, parent), child, 'Sparky') parent, child = 'residence.type'.split('.') setattr(getattr(p, parent), child, 'Sparky') print p.__dict__
This is simpler than the other answers for this particular use case.