I have an object (Person
) that has multiple subobjects (Pet, Residence
) as properties. I want to be able to dynamically set the properties of these subobjects like so:
class 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 = species class Residence(object): def __init__(self,type='House',sqft=None): self.type = type self.sqft=sqft if __name__=='__main__': p=Person() setattr(p,'pet.name','Sparky') setattr(p,'residence.type','Apartment') print p.__dict__
Currently I get the wrong output: {'pet': <__main__.Pet object at 0x10c5ec050>, 'residence': <__main__.Residence object at 0x10c5ec0d0>, 'pet.name': 'Sparky', 'residence.type': 'Apartment'}
As you can see, instead of setting the name
attribute on the Pet
subobject of the Person
, a new attribute pet.name
is created on the Person
.
I cannot specify
person.pet
tosetattr()
because different sub-objects will be set by the same method, which parses some text and fills in the object attributes if/when a relevant key is found.Is there a easy/builtin way to accomplish this?
Or perhaps I need to write a recursive function to parse the string and call
getattr()
multiple times until the necessary subobject is found and then callsetattr()
on that found subobject?
Advertisement
Answer
You could use functools.reduce
:
import functools def 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_31174427 def 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 functools class 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 = species class Residence(object): def __init__(self,type='House',sqft=None): self.type = type self.sqft=sqft def 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