Подтвердить что ты не робот

Почему `getattr` не поддерживает последовательные извлечения атрибутов?

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
Мне показалось очень естественным считать последнее. Я уверен, что для этого есть веская причина. Что это?
4b9b3361

Ответ 1

Вы не можете поместить период в функцию getattr, потому что getattr - это прямой поиск в словаре объекта.

Если вы используете функцию "dir" на a, вы увидите ключи словаря, соответствующие вашим атрибутам объекта. В этом случае "b.c" не находится в наборе ключей словаря.

Единственный способ сделать это с помощью getattr - это вложить вызовы:

getattr(getattr(a, "b"), "c")

К счастью, стандартная библиотека имеет лучшее решение!

import operator
operator.attrgetter("b.c")(a)

Ответ 2

Встроенная функция reduce на Python позволяет использовать функции, которые вы ищете. Вот простая вспомогательная функция, которая выполнит задание:

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'). Это будет обобщать операцию, не беспокоясь о счетчике точечной операции, имеющейся в строке.