Getattr And Setattr On Nested Subobjects / Chained Properties?
Solution 1:
You could use functools.reduce
:
import functools
defrsetattr(obj, attr, val):
pre, _, post = attr.rpartition('.')
returnsetattr(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_31174427defrgetattr(obj, attr, *args):
def_getattr(obj, attr):
returngetattr(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
classPerson(object):
def__init__(self):
self.pet = Pet()
self.residence = Residence()
classPet(object):
def__init__(self,name='Fido',species='Dog'):
self.name = name
self.species = species
classResidence(object):
def__init__(self,type='House',sqft=None):
self.type = type
self.sqft=sqft
defrsetattr(obj, attr, val):
pre, _, post = attr.rpartition('.')
returnsetattr(rgetattr(obj, pre) if pre else obj, post, val)
defrgetattr(obj, attr, *args):
def_getattr(obj, attr):
returngetattr(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 missingprint(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)
# Sparkyprint(p.residence.type)
# Apartment
Solution 2:
For an out of the box solution, you can use operator.attrgetter
:
from operator import attrgetter
attrgetter(dotted_path)(obj)
Solution 3:
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.
Solution 4:
unutbu's answer (https://stackoverflow.com/a/31174427/2683842) has a "bug". After getattr()
fails and is replaced by default
, it continues calling getattr
on default
.
Example: rgetattr(object(), "nothing.imag", 1)
should equal 1
in my opinion, but it returns 0
:
getattr(object(), 'nothing', 1)
== 1.getattr(1, 'imag', 1)
== 0 (since 1 is real and has no complex component).
Solution
I modified rgetattr to return default
at the first missing attribute:
import functools
DELIMITER = "."defrgetattr(obj, path: str, *default):
"""
:param obj: Object
:param path: 'attr1.attr2.etc'
:param default: Optional default value, at any point in the path
:return: obj.attr1.attr2.etc
"""
attrs = path.split(DELIMITER)
try:
return functools.reduce(getattr, attrs, obj)
except AttributeError:
if default:
return default[0]
raise
Solution 5:
I made a simple version based on ubntu's answer called magicattr that also works on attrs, lists, and dicts by parsing and walking the ast.
For example, with this class:
classPerson:
settings = {
'autosave': True,
'style': {
'height': 30,
'width': 200
},
'themes': ['light', 'dark']
}
def__init__(self, name, age, friends):
self.name = name
self.age = age
self.friends = friends
bob = Person(name="Bob", age=31, friends=[])
jill = Person(name="Jill", age=29, friends=[bob])
jack = Person(name="Jack", age=28, friends=[bob, jill])
You can do this
# Nothing new
assert magicattr.get(bob, 'age') == 31# Lists
assert magicattr.get(jill, 'friends[0].name') == 'Bob'
assert magicattr.get(jack, 'friends[-1].age') == 29# Dict lookups
assert magicattr.get(jack, 'settings["style"]["width"]') == 200# Combination of lookups
assert magicattr.get(jack, 'settings["themes"][-2]') == 'light'
assert magicattr.get(jack, 'friends[-1].settings["themes"][1]') == 'dark'# Setattr
magicattr.set(bob, 'settings["style"]["width"]', 400)
assert magicattr.get(bob, 'settings["style"]["width"]') == 400# Nested objects
magicattr.set(bob, 'friends', [jack, jill])
assert magicattr.get(jack, 'friends[0].friends[0]') == jack
magicattr.set(jill, 'friends[0].age', 32)
assert bob.age == 32
It also won't let you/someone call functions or assign a value since it doesn't use eval or allow Assign/Call nodes.
with pytest.raises(ValueError) as e:
magicattr.get(bob, 'friends = [1,1]')
# Nice try, function calls are not allowed
with pytest.raises(ValueError):
magicattr.get(bob, 'friends.pop(0)')
Post a Comment for "Getattr And Setattr On Nested Subobjects / Chained Properties?"