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

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

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

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

Ответ 1

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

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__).
  • Вызывая метод super() __init__(), мы убедились, что он (уже) ведет себя точно так же, как словарь, так как эта функция вызывает весь код экземпляра словаря.

Одна из причин, почему 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!"

Ответ 2

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

Ответ 3

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

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

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

Ответ 4

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

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

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

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

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

>>> KEY = 'spam'
>>> d[KEY] = 1
>>> # Several lines of miscellaneous code here...
... assert d.spam == 1

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

Кроме того, если вы измените значение KEY следующим образом (но не сменив d.spam), теперь вы получите:

>>> KEY = 'foo'
>>> d[KEY] = 1
>>> # Several lines of miscellaneous code here...
... assert d.spam == 1
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
AttributeError: 'C' object has no attribute 'spam'

ИМО, не стоит усилий.

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

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

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

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

>>> C = 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 = 'spam eggs ham'
>>> VALS = [1, 2, 3]
>>> d = {k: v for k, v in zip(KEYS.split(' '), VALS)}
>>> assert d == {'spam': 1, 'eggs': 2, 'ham': 3}
>>>

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

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

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

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

>>> C = type('C', (object,), {})
>>> d = C()
>>> d.spam = 1
>>> d.eggs = 2
>>> d.ham = 3
>>> assert d.__dict__ == {'spam': 1, 'eggs': 2, 'ham': 3}


Что я обновляю, чтобы ответить на следующий вопрос в комментариях

В комментариях (ниже) Элмо спрашивает:

Что, если вы хотите пойти глубже? (ссылаясь на тип (...))

Хотя я никогда не использовал этот вариант использования (опять же, я стараюсь использовать вложенный dict, для обеспечения согласованности), работает следующий код:

>>> C = type('C', (object,), {})
>>> d = C()
>>> for x in 'spam eggs ham'.split():
...     setattr(d, x, C())
...     i = 1
...     for y in 'one two three'.split():
...         setattr(getattr(d, x), y, i)
...         i += 1
...
>>> assert d.spam.__dict__ == {'one': 1, 'two': 2, 'three': 3}

Ответ 6

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

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

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

Ответ 7

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

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__

Ответ 8

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

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

Ответ 9

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

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

Ответ 10

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

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

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

rec = record('Model', {
    'train_op': train_op,
    'loss': loss,
})

print rec.loss(..)

Ответ 11

Это не относится к исходному вопросу, но должно быть полезно для людей, которые, как и я, заканчивают здесь, когда ищут 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'

Ответ 12

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

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

Ответ 13

Я создал это на основе ввода из этого потока. Мне нужно использовать 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

Ответ 14

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

Ответ 15

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

class Bunch(dict):                                                              
    """ Scikit Learn container object                                         

    Dictionary-like object that exposes its keys as attributes.                 
    >>> b = Bunch(a=1, b=2)                                                     
    >>> b['b']                                                                  
    2                                                                           
    >>> b.b                                                                     
    2                                                                           
    >>> b.c = 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 и переход на проверку фактических атрибутов. setstaet является исправлением для исправления для травления/рассыпания "пучков" - если inerested проверяет https://github.com/scikit-learn/scikit-learn/issues/6196

Ответ 16

Как насчет Prodict, маленький класс Python, который я написал, чтобы управлять ими все :)

Кроме того, вы получаете автоматическое завершение кода, рекурсивные объекты и автоматическое преобразование типов !

Вы можете сделать именно то, что вы просили:

p = Prodict()
p.foo = 1
p.bar = "baz"

Пример 1: Тип намека

class Country(Prodict):
    name: str
    population: int

turkey = Country()
turkey.name = 'Turkey'
turkey.population = 79814871

auto code complete

Пример 2: Преобразование автоматического типа

germany = Country(name='Germany', population='82175700', flag_colors=['black', 'red', 'yellow'])

print(germany.population)  # 82175700
print(type(germany.population))  # <class 'int'>

print(germany.flag_colors)  # ['black', 'red', 'yellow']
print(type(germany.flag_colors))  # <class 'list'>

Ответ 17

Позвольте мне опубликовать еще одну реализацию, которая опирается на ответ Кинваиса, но объединяет идеи из 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

Ответ 18

Вы можете сделать это, используя этот класс, который я только что сделал. С помощью этого класса вы можете использовать объект 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']

Ответ 19

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

Ответ 20

Решение:

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 AttributeError("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__()

Ответ 21

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

NeoBunch

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

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

Ответ 22

Это не "хороший" ответ, но я думал, что это отличное (он не обрабатывает вложенные dicts в текущей форме). Просто оберните свой 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'}

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

Ответ 23

Каковы были бы оговорки и ловушки доступа к ключам диктатора таким образом?

Как предполагает @Henry, одна из причин, по которой точка-доступ не может использоваться в dicts, заключается в том, что он ограничивает имена ключей ключа действительными переменными python, тем самым ограничивая все возможные имена.

Ниже приведены примеры того, почему пунктирный доступ не будет полезен вообще, учитывая dict, d:

Период действия

Следующие атрибуты недействительны в Python:

d.1_foo                           # enumerated names
d./bar                            # path names
d.21.7, d.12:30                   # decimals, time
d.""                              # empty strings
d.john doe, d.denny             # spaces, misc punctuation 
d.3 * x                           # expressions  

Стиль

Соглашения PEP8 накладывают мягкое ограничение на присвоение атрибутов:

A. Зарезервированное ключевое слово (или встроенная функция):

d.in
d.False, d.True
d.max, d.min
d.sum
d.id

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

B. Правило случая о методах и именах переменных:

Имена переменных следуют тому же соглашению, что и имена функций.

d.Firstname
d.Country

Используйте правила именования функций: в нижнем регистре со словами, разделенными символами подчеркивания, для повышения удобочитаемости.


Иногда эти проблемы возникают в таких библиотеках, как панды, что позволяет использовать пунктирный доступ столбцов DataFrame по имени. Механизм по умолчанию для устранения ограничений именования также является обозначением массива - строка в скобках.

Если эти ограничения не применяются к вашему варианту использования, существует несколько вариантов структуры данных с точками доступа.

Ответ 24

Вы можете использовать dict_to_obj https://pypi.org/project/dict-to-obj/ Он делает именно то, что вы просили

From dict_to_obj import DictToObj
a = {
'foo': True
}
b = DictToObj(a)
b.foo
True