getattr and setattr on nested subobjects / chained properties? getattr and setattr on nested subobjects / chained properties? python python

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.