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
Мне показалось очень естественным считать последнее. Я уверен, что для этого есть веская причина. Что это?Почему `getattr` не поддерживает последовательные извлечения атрибутов?
Ответ 1
Вы не можете поместить период в функцию getattr, потому что getattr - это прямой поиск в словаре объекта.
Если вы используете функцию "dir" на a, вы увидите ключи словаря, соответствующие вашим атрибутам объекта. В этом случае "b.c" не находится в наборе ключей словаря.
Единственный способ сделать это с помощью getattr
- это вложить вызовы:
getattr(getattr(a, "b"), "c")
К счастью, стандартная библиотека имеет лучшее решение!
import operator
operator.attrgetter("b.c")(a)
Ответ 2
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
Тестирование,
>>> 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'>
Ответ 3
Потому что getattr
не работает. getattr
получает атрибут данного объекта (первый аргумент) с заданным именем (второй аргумент). Итак, ваш код:
getattr(a, "b.c") # this raises an AttributeError
означает: Доступ к атрибуту "b.c" объекта, на который ссылается "a". Очевидно, что у вашего объекта нет атрибута "b.c
".
Чтобы получить атрибут "c", вы должны использовать два вызова getattr
:
getattr(getattr(a, "b"), "c")
Разверните его для лучшего понимания:
b = getattr(a, "b")
c = getattr(b, "c")
Ответ 4
Я думаю, что ваша путаница возникает из-за того, что прямое обозначение точки (ex a.b.c
) обращается к тем же параметрам, что и getattr()
, но логика разбора отличается. Хотя оба они по существу относятся к объекту __dict__
, getattr()
не привязаны к более строгим требованиям к точкам доступным атрибутам. Например,
setattr(foo, 'Big fat ugly string. But you can hash it.', 2)
Действительно, поскольку эта строка просто становится хэш-ключом в foo.__dict__
, но
foo.Big fat ugly string. But you can hash it. = 2
и
foo.'Big fat ugly string. But you can hash it.' = 2
являются синтаксическими ошибками, потому что теперь вы просите интерпретатора разобрать эти вещи как исходный код, и это не работает.
Оборотная сторона этого состоит в том, что в то время как foo.b.c
эквивалентно foo.__dict__['b'].__dict__['c']
, getattr(foo, 'b.c')
эквивалентно foo.__dict__['b.c']
. Поэтому getattr
работает не так, как вы ожидаете.
Ответ 5
Я думаю, что самый прямой способ достичь того, что вы хотите, - это operator.attrgetter
.
>>> import operator
>>> class B():
... c = 'foo'
...
>>> class A():
... b = B()
...
>>> a = A()
>>> operator.attrgetter('b.c')(a)
'foo'
Если атрибут не существует, вы получите AttributeError
>>> operator.attrgetter('b.d')(a)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: B instance has no attribute 'd'
Ответ 6
Что следует вернуть getattr('a.b', {'a': None}, 'default-value'}
? Должен ли он поднять AttributeError
или просто вернуть 'default-value'
? Вот почему сложные клавиши, если они введены в getattr
, затруднят использование.
Таким образом, более естественно рассматривать функцию getattr(..)
как метод get
словаря атрибутов объекта.
Ответ 7
Вы можете вызывать множественный getattr, не вызывая функцию внутри функции, разбивая операторы точек и выполняя getattr() для каждого оператора точки
def multi_getattr(self,obj, attr, default = None):
attributes = attr.split(".")
for i in attributes:
try:
obj = getattr(obj, i)
except AttributeError:
if default:
return default
else:
raise
return obj
Если вы хотите вызвать a.b.c.d, вы можете сделать это через a.multi_getattr ('b.c.d'). Это будет обобщать операцию, не беспокоясь о счетчике точечной операции, имеющейся в строке.