Динамически получать элементы dict через getattr?

Я хочу динамически запрашивать, какие объекты из класса я хотел бы получить. Мне нравится getattr, и он отлично работает с объектами верхнего уровня в классе. Однако я хотел бы также указать подэлементы.

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

Как я могу получить элементы словаря объекта через строку?


person user394430    schedule 01.02.2012    source источник
comment
getattr(myobj, "d")['a'] сделал бы это, но я думаю, это не то, что вам нужно.   -  person Felix Kling    schedule 01.02.2012
comment
Нет, я не этого хочу. Я хочу, чтобы метод был универсальным, чтобы я мог запрашивать атрибут объекта или податрибут / подобъект / подэлемент объекта с динамически создаваемой строкой. Я могу сделать это с помощью eval, но не хочу.   -  person user394430    schedule 02.02.2012
comment
Вам нужно будет создать некоторую сложную функцию, которая анализирует строку, которая по сути будет уменьшенной версией eval. Зачем тебе это кстати? Похоже, должно быть лучшее решение ...   -  person Luiz C.    schedule 03.02.2012


Ответы (4)


Причина, по которой вы получаете сообщение об ошибке, заключается в том, что getattr(myobj, "d['a']") ищет атрибут с именем d['a'] на объекте, а его нет. Ваш атрибут называется d, и это словарь. Если у вас есть ссылка на словарь, затем вы можете получить доступ к его элементам.

mydict = getattr(myobj, "d")
val    = mydict["a"]

Или, как показали другие, вы можете объединить это за один шаг (я показал их как два, чтобы лучше проиллюстрировать, что на самом деле происходит):

val = getattr(myobj, "d")["a"]

Ваш вопрос подразумевает, что вы думаете, что элементы словаря в объекте являются «субэлементами» объекта. Однако элемент в словаре отличается от атрибута объекта. (Однако getattr() не будет работать и с чем-то вроде o.a; он просто получает один атрибут одного объекта. Если это тоже объект, и вы хотите получить один из его атрибутов, это другой getattr().)

Вы можете довольно легко написать функцию, которая проходит по пути атрибута (заданному в строке) и пытается разрешить каждое имя либо как ключ словаря, либо как атрибут:

def resolve(obj, attrspec):
    for attr in attrspec.split("."):
        try:
            obj = obj[attr]
        except (TypeError, KeyError):
            obj = getattr(obj, attr)
    return obj

Основная идея здесь заключается в том, что вы выбираете путь и для каждого компонента пути пытаетесь найти либо элемент в контейнере, похожем на словарь, либо атрибут объекта. Когда дойдете до конца пути, верните то, что у вас есть. Ваш пример будет resolve(myobj, "d.a")

person kindall    schedule 01.02.2012
comment
Спасибо за комментарий. Вы точно понимаете проблему, но я хочу, чтобы метод был универсальным, чтобы я мог запрашивать атрибут объекта или субатрибут / подобъект / подэлемент объекта с динамически создаваемой строкой. Я могу сделать это с помощью eval, но не хочу. Есть другие идеи? - person user394430; 02.02.2012

Вы просто используете квадратные скобки, чтобы получить элемент словаря:

val = getattr(myobj, "d")["a"]

Это установит val в 1.

person NPE    schedule 01.02.2012
comment
Спасибо, но я не этого хочу. (Скопировано сверху) Я хочу, чтобы метод был универсальным, чтобы я мог запрашивать атрибут объекта или податрибут / подобъект / подэлемент объекта с динамически создаваемой строкой. Я могу сделать это с помощью eval, но не хочу. - person user394430; 02.02.2012

Если вам нужно, чтобы элемент словаря также был динамическим, вам нужно будет вызвать get для результата getattr:

value = getattr(myobj, 'd').get('a')
person Daniel Roseman    schedule 01.02.2012
comment
Я бы даже пошел дальше и сделал getattr(myobj, 'd', {}).get('a') - person jdi; 02.02.2012

Благодаря ответу Киндалла я обнаружил, что следующее хорошо работает для ключей dict, которые являются укусами.

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")
person user394430    schedule 05.02.2012