I want to dynamically query which objects from a class I would like to retrieve. getattr seems like what I want, and it performs fine for top-level objects in the class. However, I'd like to also specify sub-elements.
class MyObj(object):
def __init__(self):
self.d = {'a':1, 'b':2}
self.c = 3
myobj = MyObj()
val = getattr(myobj, "c")
print val # Correctly prints 3
val = getattr(myobj, "d['a']") # Seemingly incorrectly formatted query
print val # Throws an AttributeError
How can I get the object's dictionary elements via a string?
The reason you're getting an error is that getattr(myobj, "d['a']")
looks for an attribute named d['a']
on the object, and there isn't one. Your attribute is named d
and it's a dictionary. Once you have a reference to the dictionary, then you can access items in it.
mydict = getattr(myobj, "d")
val = mydict["a"]
Or as others have shown, you can combine this in one step (I showed it as two to better illustrate what is actually happening):
val = getattr(myobj, "d")["a"]
Your question implies that you think that items of a dictionary in an object are "sub-elements" of the object. An item in a dictionary, however, is a different thing from an attribute of an object. (getattr()
wouldn't work with something like o.a
either, though; it just gets one attribute of one object. If that's an object too and you want to get one of its attributes, that's another getattr()
.)
You can pretty easily write a function that walks an attribute path (given in a string) and attempts to resolve each name either as a dictionary key or an attribute:
def resolve(obj, attrspec):
for attr in attrspec.split("."):
try:
obj = obj[attr]
except (TypeError, KeyError):
obj = getattr(obj, attr)
return obj
The basic idea here is that you take a path and for each component of the path, try to find either an item in a dictionary-like container or an attribute on an object. When you get to the end of the path, return what you've got. Your example would be resolve(myobj, "d.a")
You simply use square brackets to get the dictionary's element:
val = getattr(myobj, "d")["a"]
That'll set val
to 1
.
If you need the dictionary item to be dynamic as well, you'll need to call get
on the result of getattr
:
value = getattr(myobj, 'd').get('a')
Thanks to Kindall's answer, I found the following works well for dict keys that are stings.
class Obj2(object):
def __init__(self):
self.d = {'a':'A', 'b':'B', 'c': {'three': 3, 'twothree': (2,3)}}
self.c = 4
class MyObj(object):
def __init__(self):
self.d = {'a':1, 'b':2, 'c': {'two': 2, 'onetwo': (1,2)}}
self.c = 3
self.obj2 = Obj2()
def resolve(self, obj, attrspec):
attrssplit = attrspec.split(".")
attr = attrssplit[0]
try:
obj = obj[attr]
except (TypeError, KeyError):
obj = getattr(obj, attr)
if len(attrssplit) > 1:
attrspec = attrspec.partition(".")[2] # right part of the string.
return self.resolve(obj, attrspec) # Recurse
return obj
def __getattr__(self, name):
return self.resolve(self, name)
# Test
myobj = MyObj()
print getattr(myobj, "c")
print getattr(myobj, "d.a")
print getattr(myobj, "d.c.two")
print getattr(myobj, "obj2.d.a")
print getattr(myobj, "obj2.d.c.twothree")
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With