Доступ к клавишам dict, как атрибут в Python?

Я нахожу более удобным доступ к ключам dict как obj.foo вместо obj['foo'] , поэтому я написал этот фрагмент:

 class AttributeDict(dict): def __getattr__(self, attr): return self[attr] def __setattr__(self, attr, value): self[attr] = value 

Тем не менее, я полагаю, что должна быть какая-то причина, что Python не предоставляет эту функциональность из коробки. Каковы были бы оговорки и ловушки доступа к ключам диктатора таким образом?

21 Solutions collect form web for “Доступ к клавишам dict, как атрибут в Python?”

Лучший способ сделать это:

 class AttrDict(dict): def __init__(self, *args, **kwargs): super(AttrDict, self).__init__(*args, **kwargs) self.__dict__ = self 

Некоторые профи:

  • Это действительно работает!
  • Никакие методы класса словаря не затенены (например, .keys() работают просто отлично)
  • Атрибуты и элементы всегда синхронизируются
  • Попытка получить доступ к несуществующему ключу как атрибуту правильно повышает значение AttributeError вместо KeyError

Минусы:

  • Такие методы, как .keys() , не будут работать нормально, если они будут перезаписаны входящими данными
  • Вызывает утечку памяти в Python <2.7.4 / Python3 <3.2.3
  • Pylint идет бананы с E1123(unexpected-keyword-arg) и E1103(maybe-no-member)
  • Для непосвященных это кажется чистой магией.

Краткое объяснение того, как это работает

  • Все объекты python внутренне хранят свои атрибуты в словаре, который называется __dict__ .
  • Нет требования, чтобы внутренний словарь __dict__ должен был быть «просто простым dict», поэтому мы можем назначить любой подкласс dict() для внутреннего словаря.
  • В нашем случае мы просто присваиваем AttrDict() мы создаем (как мы находимся в __init__ ).
  • Вызвав метод __init__() super() мы убедились, что он (уже) ведет себя точно так же, как словарь, поскольку эта функция вызывает весь код экземпляра словаря .

Одна из причин, почему Python не предоставляет эту функциональность из коробки

Как отмечено в списке «cons», это объединяет пространство имен хранимых ключей (которое может исходить из произвольных и / или ненадежных данных!) С пространством имен встроенных атрибутов dict-метода. Например:

 d = AttrDict() d.update({'items':["jacket", "necktie", "trousers"]}) for k, v in d.items(): # TypeError: 'list' object is not callable print "Never reached!" 

Вы можете иметь все юридические строковые символы как часть ключа, если используете нотацию массива. Например, obj['!#$%^&*()_']

Из этого другого вопроса SO есть отличный пример реализации, который упрощает ваш существующий код. Как насчет:

 class AttributeDict(dict): __getattr__ = dict.__getitem__ __setattr__ = dict.__setitem__ 

Гораздо более лаконичный и не оставляет места для дополнительного использования в ваших __getattr__ и __setattr__ в будущем.

В чем я отвечаю на вопрос, который был задан

Почему Python не предлагает его из коробки?

Я подозреваю, что это связано с Zen Python : «Должен быть один – и желательно только один – очевидный способ сделать это». Это создаст два очевидных способа доступа к значениям из словарей: obj['key'] и obj.key .

Предостережения и ошибки

К ним относятся возможное отсутствие ясности и путаницы в коде. т. е. следующее может смутить кого то другого, кто собирается поддерживать ваш код на более поздний срок или даже на вас, если вы не вернетесь в него некоторое время. Опять же, от Дзэн : «Читаемость считается!»

 >>> KEY = 'foo' >>> d[KEY] = 1 >>> if d.foo == 1: ... ... ... 

Если d экземпляр или KEY определен, или d[KEY] назначается далеко от того, где используется d.foo , это может легко привести к путанице в отношении того, что делается, поскольку это не обычная идиома. Я знаю, что это может смутить меня.

Другие предметы

Как отмечали другие, вы можете использовать любой хешируемый объект (а не только строку) в качестве ключа dict. Например,

 >>> d = {(2, 3): True,} >>> assert d[(2, 3)] is True >>> 

является законным, но

 >>> C = type('type_C', (object,), {(2, 3): True}) >>> d = C() >>> assert d.(2, 3) is True File "<stdin>", line 1 d.(2, 3) ^ SyntaxError: invalid syntax >>> getattr(d, (2, 3)) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: getattr(): attribute name must be string >>> 

не является. Это дает вам доступ ко всему диапазону печатаемых символов или других хешируемых объектов для ваших ключей словаря, которых у вас нет при доступе к атрибуту объекта. Это делает возможной такую ​​магию, как метаклас кешированного объекта, как рецепт из Cookbook Python (глава 9) .

Где я редактирую

Я предпочитаю эстетику spam.eggs над spam['eggs'] (я думаю, что он выглядит более чистым), и я действительно начал жаждать эту функциональность, когда встретил namedtuple . Но удобство быть в состоянии сделать следующие козыри.

 >>> KEYS = 'foo bar baz' >>> VALS = [1, 2, 3] >>> d = {k: v for k, v in zip(KEYS.split(' '), VALS)} >>> assert d == {'foo': 1, 'bar': 2, 'baz': 3} >>> 

Это простой пример, но я часто использую dicts в разных ситуациях, чем использовать нотацию obj.key (т. obj.key Когда мне нужно читать prefs из XML-файла). В других случаях, когда я испытываю соблазн создать экземпляр динамического класса и пощекотать некоторые атрибуты по эстетическим соображениям, я продолжаю использовать dict для согласованности, чтобы повысить удобочитаемость.

Я уверен, что OP долгое время разрешал это с его удовлетворением, но если он все еще хочет эту функциональность, я предлагаю загрузить один из пакетов из pypi, который предоставляет его:

  • Букет – это тот, с которым я больше знаком. Подкласс dict , поэтому у вас есть все эти функции.
  • AttrDict также выглядит так же хорошо, но я не так хорошо знаком с ним и не просматривал источник настолько подробно, как у меня есть Bunch .
  • Как отмечалось в комментариях Ротарети, Bunch устарел, но есть активная вилка под названием Munch .

Однако, чтобы улучшить удобочитаемость его кода, я настоятельно рекомендую не смешивать его стили нотации. Если он предпочитает это обозначение, то он должен просто создать экземпляр динамического объекта, добавить к нему нужные ему атрибуты и назвать его днем:

 >>> C = type('type_C', (object,), {}) >>> d = C() >>> d.foo = 1 >>> d.bar = 2 >>> d.baz = 3 >>> assert d.__dict__ == {'foo': 1, 'bar': 2, 'baz': 3} >>> 

Что делать, если вам нужен ключ, который был методом, например __eq__ или __getattr__ ?

И вы не сможете получить запись, которая не начиналась с буквы, поэтому использование 0343853 в качестве ключа отсутствует.

А что, если вы не хотите использовать строку?

Caveat emptor: По некоторым причинам подобные классы, похоже, разбивают пакет многопроцессорности. Я просто боролся с этой ошибкой на некоторое время, прежде чем найти это SO: Исключение исключений в многопроцессорности python

Вы можете вытащить удобный класс контейнера из стандартной библиотеки:

 from argparse import Namespace 

чтобы избежать копирования кодовых битов. Нет стандартного доступа к словарю, но легко получить его обратно, если вы действительно этого хотите. Код в argparse прост,

 class Namespace(_AttributeHolder): """Simple object for storing attributes. Implements equality by attribute names and values, and provides a simple string representation. """ def __init__(self, **kwargs): for name in kwargs: setattr(self, name, kwargs[name]) __hash__ = None def __eq__(self, other): return vars(self) == vars(other) def __ne__(self, other): return not (self == other) def __contains__(self, key): return key in self.__dict__ 

Кортежи можно использовать для ключей dict. Как вы получите доступ к кортежу в своей конструкции?

Кроме того, namedtuple – удобная структура, которая может предоставлять значения через доступ к атрибуту.

Это не работает в общности. Не все действительные ключи dict образуют адресные атрибуты («ключ»). Итак, вам нужно быть осторожным.

Объекты Python – это в основном словари. Поэтому я сомневаюсь, что есть большая производительность или другое наказание.

Не нужно писать свои собственные, поскольку setattr () и getattr () уже существуют.

Преимущество объектов класса, вероятно, вступает в игру в определении класса и наследовании.

Я создал это на основе ввода из этого потока. Мне нужно использовать odict, хотя, поэтому мне пришлось переопределить get и установить attr. Я думаю, что это должно работать для большинства специальных целей.

Использование выглядит следующим образом:

 # Create an ordered dict normally... >>> od = OrderedAttrDict() >>> od["a"] = 1 >>> od["b"] = 2 >>> od OrderedAttrDict([('a', 1), ('b', 2)]) # Get and set data using attribute access... >>> od.a 1 >>> od.b = 20 >>> od OrderedAttrDict([('a', 1), ('b', 20)]) # Setting a NEW attribute only creates it on the instance, not the dict... >>> od.c = 8 >>> od OrderedAttrDict([('a', 1), ('b', 20)]) >>> od.c 8 

Класс:

 class OrderedAttrDict(odict.OrderedDict): """ Constructs an odict.OrderedDict with attribute access to data. Setting a NEW attribute only creates it on the instance, not the dict. Setting an attribute that is a key in the data will set the dict data but will not create a new instance attribute """ def __getattr__(self, attr): """ Try to get the data. If attr is not a key, fall-back and get the attr """ if self.has_key(attr): return super(OrderedAttrDict, self).__getitem__(attr) else: return super(OrderedAttrDict, self).__getattr__(attr) def __setattr__(self, attr, value): """ Try to set the data. If attr is not a key, fall-back and set the attr """ if self.has_key(attr): super(OrderedAttrDict, self).__setitem__(attr, value) else: super(OrderedAttrDict, self).__setattr__(attr, value) 

Это довольно крутой шаблон, уже упомянутый в потоке, но если вы просто хотите взять dict и преобразовать его в объект, который работает с автозаполнением в среде IDE и т. Д .:

 class ObjectFromDict(object): def __init__(self, d): self.__dict__ = d 

По-видимому, теперь для этого есть библиотека – https://pypi.python.org/pypi/attrdict – которая реализует эту точную функциональность плюс рекурсивное слияние и загрузку json. Возможно, стоит посмотреть.

Это не относится к исходному вопросу, но должно быть полезно для людей, которые, как и я, заканчивают здесь, когда ищут lib, который предоставляет эту функциональность.

Addict это отличная библиотека для этого: https://github.com/mewwts/addict, он заботится о многих проблемах, упомянутых в предыдущих ответах.

Пример из документов:

 body = { 'query': { 'filtered': { 'query': { 'match': {'description': 'addictive'} }, 'filter': { 'term': {'created_by': 'Mats'} } } } } 

С наркоманом:

 from addict import Dict body = Dict() body.query.filtered.query.match.description = 'addictive' body.query.filtered.filter.term.created_by = 'Mats' 

Вот краткий пример неизменяемых записей с использованием встроенных collections.namedtuple :

 def record(name, d): return namedtuple(name, d.keys())(**d) 

и пример использования:

 rec = record('Model', { 'train_op': train_op, 'loss': loss, }) print rec.loss(..) 
 class AttrDict(dict): def __init__(self): self.__dict__ = self if __name__ == '____main__': d = AttrDict() d['ray'] = 'hope' d.sun = 'shine' >>> Now we can use this . notation print d['ray'] print d.sun 

Как отметил Doug, есть пакет Bunch, который вы можете использовать для достижения функциональности obj.key . На самом деле есть более новая версия, называемая

NeoBunch

У него есть отличная возможность конвертировать ваш dict в объект NeoBunch через функцию neobunchify . Я часто использую шаблоны Mako и передаю данные, поскольку объекты NeoBunch делают их более читабельными, поэтому, если вы, оказывается, закончите использовать обычный dict в своей программе Python, но хотите, чтобы нотация точек в шаблоне Mako вы могли использовать так:

 from mako.template import Template from neobunch import neobunchify mako_template = Template(filename='mako.tmpl', strict_undefined=True) data = {'tmpl_data': [{'key1': 'value1', 'key2': 'value2'}]} with open('out.txt', 'w') as out_file: out_file.write(mako_template.render(**neobunchify(data))) 

И шаблон Мако может выглядеть так:

 % for d in tmpl_data: Column1 Column2 ${d.key1} ${d.key2} % endfor 

Это не «хороший» ответ, но я думал, что это отличное (он не обрабатывает вложенные слова в текущей форме). Просто оберните свой dict в функцию:

 def make_funcdict(d={}, **kwargs) def funcdict(d={}, **kwargs): funcdict.__dict__.update(d) funcdict.__dict__.update(kwargs) return funcdict.__dict__ funcdict(d, **kwargs) return funcdict 

Теперь у вас немного другой синтаксис. Для доступа к элементам dict в качестве атрибутов используется f.key . Чтобы получить доступ к элементам dict (и другим методам dict) обычным способом, сделайте f()['key'] и мы можем удобно обновить dict, вызвав f с помощью аргументов ключевого слова и / или словаря

пример

 d = {'name':'Henry', 'age':31} d = make_funcdict(d) >>> for key in d(): ... print key ... age name >>> print d.name ... Henry >>> print d.age ... 31 >>> d({'Height':'5-11'}, Job='Carpenter') ... {'age': 31, 'name': 'Henry', 'Job': 'Carpenter', 'Height': '5-11'} 

И вот оно. Я буду рад, если кто-то предложит преимущества и недостатки этого метода.

Вы можете сделать это, используя этот класс, который я только что сделал. С помощью этого класса вы можете использовать объект Map как другой словарь (включая сериализацию json) или с помощью точечной нотации. Надеюсь, вам помогут:

 class Map(dict): """ Example: m = Map({'first_name': 'Eduardo'}, last_name='Pool', age=24, sports=['Soccer']) """ def __init__(self, *args, **kwargs): super(Map, self).__init__(*args, **kwargs) for arg in args: if isinstance(arg, dict): for k, v in arg.iteritems(): self[k] = v if kwargs: for k, v in kwargs.iteritems(): self[k] = v def __getattr__(self, attr): return self.get(attr) def __setattr__(self, key, value): self.__setitem__(key, value) def __setitem__(self, key, value): super(Map, self).__setitem__(key, value) self.__dict__.update({key: value}) def __delattr__(self, item): self.__delitem__(item) def __delitem__(self, key): super(Map, self).__delitem__(key) del self.__dict__[key] 

Примеры использования:

 m = Map({'first_name': 'Eduardo'}, last_name='Pool', age=24, sports=['Soccer']) # Add new key m.new_key = 'Hello world!' print m.new_key print m['new_key'] # Update values m.new_key = 'Yay!' # Or m['new_key'] = 'Yay!' # Delete key del m.new_key # Or del m['new_key'] 

Решение:

 DICT_RESERVED_KEYS = vars(dict).keys() class SmartDict(dict): """ A Dict which is accessible via attribute dot notation """ def __init__(self, *args, **kwargs): """ :param args: multiple dicts ({}, {}, ..) :param kwargs: arbitrary keys='value' If ``keyerror=False`` is passed then not found attributes will always return None. """ super(SmartDict, self).__init__() self['__keyerror'] = kwargs.pop('keyerror', True) [self.update(arg) for arg in args if isinstance(arg, dict)] self.update(kwargs) def __getattr__(self, attr): if attr not in DICT_RESERVED_KEYS: if self['__keyerror']: return self[attr] else: return self.get(attr) return getattr(self, attr) def __setattr__(self, key, value): if key in DICT_RESERVED_KEYS: raise TypeError("You cannot set a reserved name as attribute") self.__setitem__(key, value) def __copy__(self): return self.__class__(self) def copy(self): return self.__copy__() 

Позвольте мне опубликовать еще одну реализацию, которая основывается на ответе Кинваиса, но объединяет идеи из AttributeDict, предложенные в http://databio.org/posts/python_AttributeDict.html .

Преимущество этой версии заключается в том, что она также работает для вложенных словарей:

 class AttrDict(dict): """ A class to convert a nested Dictionary into an object with key-values that are accessible using attribute notation (AttrDict.attribute) instead of key notation (Dict["key"]). This class recursively sets Dicts to objects, allowing you to recurse down nested dicts (like: AttrDict.attr.attr) """ # Inspired by: # http://stackoverflow.com/a/14620633/1551810 # http://databio.org/posts/python_AttributeDict.html def __init__(self, iterable, **kwargs): super(AttrDict, self).__init__(iterable, **kwargs) for key, value in iterable.items(): if isinstance(value, dict): self.__dict__[key] = AttrDict(value) else: self.__dict__[key] = value 

Чтобы добавить некоторое разнообразие в ответ, научный сборник sci-kit , это реализовано как Bunch :

 class Bunch(dict): """ Scikit Learn's container object Dictionary-like object that exposes its keys as attributes. >>> b = Bunch(a=1, b=2) >>> b['b'] 2 >>> bb 2 >>> bc = 6 >>> b['c'] 6 """ def __init__(self, **kwargs): super(Bunch, self).__init__(kwargs) def __setattr__(self, key, value): self[key] = value def __dir__(self): return self.keys() def __getattr__(self, key): try: return self[key] except KeyError: raise AttributeError(key) def __setstate__(self, state): pass 

Все, что вам нужно, это получить методы setattr и getattr – проверка getattr для ключей dict и getattr на проверку фактических атрибутов. setstaet – это исправление для исправления для травления / рассыпания «сгустков» – если inerested проверяет https://github.com/scikit-learn/scikit-learn/issues/6196

Python - лучший язык программирования в мире.