Why does `getattr` not support consecutive attribute retrievals?
class A(): pass
a = A()
b = A()
a.b = b
b.c = 1
a.b # this is b
getattr(a, "b") # so is this
a.b.c # this is 1
getattr(a, "b.c") # this raises an AttributeError
It seemed very natural to me to assume the latter. I'm sure there is a good reason for this. What is it?
You can't put a period in the getattr function because getattr is like accessing the dictionary lookup of the object (but is a little bit more complex than that, due to subclassing and other Python implementation details).
If you use the 'dir' function on a, you'll see the dictionary keys that correspond to your object's attributes. In this case, the string "b.c" isn't in the set of dictionary keys.
The only way to do this with getattr
is to nest calls:
getattr(getattr(a, "b"), "c")
Luckily, the standard library has a better solution!
import operator
operator.attrgetter("b.c")(a)
Python's built-in reduce
function enables the functionality you're looking for. Here's a simple little helper function that will get the job done:
class NoDefaultProvided(object):
pass
def getattrd(obj, name, default=NoDefaultProvided):
"""
Same as getattr(), but allows dot notation lookup
Discussed in:
http://stackoverflow.com/questions/11975781
"""
try:
return reduce(getattr, name.split("."), obj)
except AttributeError, e:
if default != NoDefaultProvided:
return default
raise
Test proof;
>>> getattrd(int, 'a')
AttributeError: type object 'int' has no attribute 'a'
>>> getattr(int, 'a')
AttributeError: type object 'int' has no attribute 'a'
>>> getattrd(int, 'a', None)
None
>>> getattr(int, 'a', None)
None
>>> getattrd(int, 'a', None)
None
>>> getattrd(int, '__class__.__name__')
type
>>> getattrd(int, '__class__')
<type 'type'>
I think your confusion arises from the fact that straight dot notation (ex a.b.c
) accesses the same parameters as getattr()
, but the parsing logic is different. While they both essentially key in to an object's __dict__
attribute, getattr()
is not bound to the more stringent requirements on dot-accessible attributes. For instance
setattr(foo, 'Big fat ugly string. But you can hash it.', 2)
Is valid, since that string just becomes a hash key in foo.__dict__
, but
foo.Big fat ugly string. But you can hash it. = 2
and
foo.'Big fat ugly string. But you can hash it.' = 2
are syntax errors because now you are asking the interpreter to parse these things as raw code, and that doesn't work.
The flip side of this is that while foo.b.c
is equivalent to foo.__dict__['b'].__dict__['c']
, getattr(foo, 'b.c')
is equivalent to foo.__dict__['b.c']
. That's why getattr
doesn't work as you are expecting.