Доступ к ключам ввода как атрибут?


303

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

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

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


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

6
stackoverflow.com/questions/3031219/… имеет аналогичное решение, но идет дальше
keflavich

1
Нашел модуль для этого на github.com/bcj/AttrDict . Я не знаю, как это соотносится с решениями здесь и в смежных вопросах.
Мэтт Уилки

Я также использовал подобные хаки, теперь я используюeasydict.EasyDict
мюон

Дополнительные способы доступа к членам словаря с помощью «.» : stackoverflow.com/questions/2352181/…
Бледно-голубая точка

Ответы:


304

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

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

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

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

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

1
Как вы думаете, утечка памяти произошла бы с таким простым объектом, как: >>> class MyD (object): ... def init __ (self, d): ... self .__ dict = d
Rafe

Вызывает утечку даже в 2,7
пи.

1
Сделайте это <= 2.7.3, поскольку это то, что я использую.
пи.

1
В примечаниях к выпуску 2.7.4 они упоминают, что это исправлено (не раньше).
Роберт Симер

1
@viveksinghggits только потому, что вы получаете доступ к вещам через ., вы не можете нарушать правила языка :) И я не хотел AttrDictбы автоматически преобразовывать содержащие пространство поля во что-то другое.
Юрик

125

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


1
@ Изката да. забавная вещь о SE, что обычно есть «главный вопрос», т.е. title и «нижний вопрос», возможно, потому что SE не любит слышать «title все говорит»; «предостережения» являются нижним здесь.
n611x007

2
Не то чтобы JavaScript был особенно хорошим примером языка программирования, но объекты в JS поддерживают как доступ к атрибутам, так и нотацию массивов, что обеспечивает удобство для общего случая и общий откат для символов, которые не являются допустимыми именами атрибутов.
Андре Карон,

@Izkata Как это отвечает на вопрос. Этот ответ просто говорит, что ключи могут иметь любое имя.
Мелаб

4
@Melab Вопрос What would be the caveats and pitfalls of accessing dict keys in this manner?(в качестве атрибутов), и ответ в том, что большинство символов, показанных здесь, не будут использоваться.
Изката

83

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

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

Гораздо более лаконичен и не оставляет места для лишних хлопот, попадающих в вашу жизнь __getattr__и __setattr__функционирующих в будущем.


Сможете ли вы вызвать AttributeDict.update или AttributeDict.get с помощью этого метода?
Дор

13
Необходимо помнить, что если вы добавляете новые атрибуты во время выполнения, они добавляются не к самому dict, а к атрибуту dict . Например d = AttributeDict(foo=1). d.bar = 1атрибут bar хранится внутри атрибута dict, но не в самом dict. печать dпоказывает только элемент foo.
P3trus

7
+1 потому что работает отлично, насколько я могу судить. @GringoSuave, @Izkata, @ P3trus Я прошу всех, кто утверждает, что это не удается, показать пример, который не работает d = AttributeDict(foo=1);d.bar = 1;print d=> У {'foo': 1, 'bar': 1}меня работает!
Дэйв Абрахамс,

4
@DaveAbrahams Прочитайте полный вопрос и посмотрите на ответы Хери, Райана и TheCommunistDuck. Дело не в том, как это сделать, а в проблемах, которые могут возникнуть .
Изката

6
Вы должны предоставить __getattr__метод, который вызывает, AttributeErrorесли данный атрибут не существует, в противном случае такие вещи, как getattr(obj, attr, default_value)не работают (т.е. не возвращаются, default_valueесли attrне существует obj)
jcdude

83

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

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

Я подозреваю, что это связано с дзен 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'

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

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

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

>>> 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
>>> 

не является. Это дает вам доступ ко всему диапазону печатных символов или других хешируемых объектов для ключей словаря, которых у вас нет при доступе к атрибуту объекта. Это делает возможной такую ​​магию, как метакласс кэшированных объектов, например рецепт из Кулинарной книги 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}
>>>

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

Я уверен, что ОП давно решил эту проблему к своему удовлетворению, но если он все еще хочет эту функциональность, тогда я предлагаю ему загрузить один из пакетов из 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}

1
Связка устарела, но есть активный
ответвление

@Rotareti - Спасибо за внимание! Это не функциональность, которую я использую, поэтому я не знал об этом.
Дуг Р.

Что делать, если вы хотите пойти еще глубже? (имеется в виду тип (...))
Оле Олдрик

6
Питон похож на перевернутый зонт, высоко под сильным дождем. Все это выглядит умным и прикольным с самого начала, через некоторое время оно начинает становиться тяжелым, а потом вдруг вы читаете некоторые встроенные гуру на SE, и все это возвращается назад со всей полезной нагрузкой на ваших плечах. Пока вы полны, вы чувствуете себя легче, и все так ясно и свежо.
Оле Олдрик


19

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

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__

2
PLUS 1 для ссылки на стандартную библиотеку, которая обращается к первому комментарию OP.
Гордон Бин

4
Python включает в себя более быстрый класс (реализованный в C) для этого случая: types.SimpleNamespace docs.python.org/dev/library/types.html#types.SimpleNamespace
Нуно Андре

18

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

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

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


Действительно, или, например, другие объекты в качестве ключей. Однако я бы отнес эту ошибку к категории «ожидаемое поведение» - с моим вопросом я больше стремился к неожиданному.
Изз ад-Дин Рухулессин

pickle.dumpиспользует__getstate__
Сис Тиммерман

12

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

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


7
Недостаток именованных кортежей в том, что они неизменны.
Изз ад-Дин Рухулессин

10
Кто-то скажет, что неизменность - это не ошибка, а особенность кортежей.
Бен автор

9

Как насчет 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

авто код завершен

Пример 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'>

2
устанавливается на python2 через pip, но не работает на python2
Ant6n

2
@ Ant6n требует Python 3.6+ из-за аннотаций типов
Рамазан Полат

8

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

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


8

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

Наркоман для этого очень полезен: 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'

8

Мне стало интересно, каково текущее состояние «dict keys as attr» в экосистеме python. Как отметили несколько комментаторов, это, вероятно, не то, что вы хотите сделать своими руками с нуля , так как есть несколько ловушек и ружей, некоторые из которых очень тонкие. Кроме того, я бы не рекомендовал использовать его Namespaceв качестве базового класса, я был на этом пути, это не красиво.

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

Претенденты (последний коммит в мастере | #commits | #contribs | охват%):

Больше не поддерживается или недостаточно поддерживается:

В настоящее время я рекомендую жевать или наркоман . У них есть большинство коммитов, участников и релизов, предлагающих здоровую базу кода с открытым исходным кодом для каждого. Они имеют самый чистый вид readme.md, 100% охват и красивый набор тестов.

У меня нет собаки в этой гонке (пока!), Кроме того, что я бросил свой собственный код dict / attr и потратил кучу времени, потому что я не знал обо всех этих вариантах :). Я могу внести свой вклад в наркоманию / жаворонок в будущем, так как я предпочел бы увидеть один солидный пакет, а не набор фрагментированных Если они вам нравятся, помогите! В частности, похоже, что munch может использовать значок codecov, а наркоман может использовать значок версии python.

наркоманы плюсы:

  • рекурсивная инициализация (foo.abc = 'bar'), подобные диктату аргументы становятся addict.Dict

наркоманы минусы:

  • тени, typing.Dictесли выfrom addict import Dict
  • Нет проверки ключа. Из-за разрешения рекурсивного инициализации, если вы неправильно написали ключ, вы просто создаете новый атрибут, а не KeyError (спасибо AljoSt)

Мунк плюсы:

  • уникальное именование
  • встроенные функции ser / de для JSON и YAML

жевать минусы:

  • никакая рекурсивная инициация / только не может инициировать один атрибут одновременно

В котором я редактирую

Много месяцев назад, когда я использовал текстовые редакторы для написания Python, в проектах, где только я или один другой разработчик, мне нравился стиль dict-attrs, возможность вставлять ключи, просто объявив foo.bar.spam = eggs. Сейчас я работаю над командами и использую IDE для всего, и я отошел от подобных структур данных и динамической типизации в целом в пользу статического анализа, функциональных методов и подсказок типов. Я начал экспериментировать с этой техникой, подклассифицируя Pstruct с объектами моего собственного дизайна:

class  BasePstruct(dict):
    def __getattr__(self, name):
        if name in self.__slots__:
            return self[name]
        return self.__getattribute__(name)

    def __setattr__(self, key, value):
        if key in self.__slots__:
            self[key] = value
            return
        if key in type(self).__dict__:
            self[key] = value
            return
        raise AttributeError(
            "type object '{}' has no attribute '{}'".format(type(self).__name__, key))


class FooPstruct(BasePstruct):
    __slots__ = ['foo', 'bar']

Это дает вам объект, который все еще ведет себя как диктованный, но также позволяет вам получить доступ к ключам, таким как атрибуты, гораздо более жестким способом. Преимущество здесь в том, что я (или несчастные потребители вашего кода) точно знаю, какие поля могут и не могут существовать, а IDE может автоматически заполнять поля. Также подклассификация vanilla dictозначает, что сериализация JSON проста. Я думаю, что следующей эволюцией в этой идее будет создание собственного генератора protobuf, который испускает эти интерфейсы, и приятным дополнением является то, что вы получаете межязыковые структуры данных и IPC через gRPC практически бесплатно.

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

Не стесняйтесь редактировать / обновлять этот пост, чтобы держать его в курсе


2
большое преимущество в addictтом, что он не будет вызывать исключения, когда вы неправильно пишете атрибут, так как он возвращает новый Dict(это необходимо для работы foo.abc = 'bar').
AljoSt

5

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

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

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

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

print rec.loss(..)

5

Просто чтобы добавить разнообразие в ответ, Sci-Kit Learn реализовал это следующим образом 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                                                                           
    >>> 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Является исправлением для исправления для засолки / unpickling «сгустков» - если inerested проверки https://github.com/scikit-learn/scikit-learn/issues/6196


3

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

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


3

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

3

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


3

Это то, что я использую

args = {
        'batch_size': 32,
        'workers': 4,
        'train_dir': 'train',
        'val_dir': 'val',
        'lr': 1e-3,
        'momentum': 0.9,
        'weight_decay': 1e-4
    }
args = namedtuple('Args', ' '.join(list(args.keys())))(**args)

print (args.lr)

Это хороший быстрый и грязный ответ. Мое единственное замечание / комментарий состоит в том, что я думаю, что конструктор namedtuple примет список строк, поэтому ваше решение может быть упрощено (я думаю) до:namedtuple('Args', list(args.keys()))(**args)
Дэн Нгуен

2

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

1
Обратите внимание, что он может использовать теневые dictметоды, например: m=Map(); m["keys"] = 42; m.keys()give TypeError: 'int' object is not callable.
bfontaine

@bfontaine Идея состоит в том, чтобы быть своего рода, field/attributeа не a method, но если вы назначите метод вместо числа, вы можете получить доступ к этому методу m.method().
Epool

2

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

1
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

1

Решение:

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

1

Каковы будут предостережения и недостатки доступа к ключам диктовки таким образом?

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

Ниже приведены примеры того, почему точка доступа не будет полезна в общем случае, если таковые имеются 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's             # spaces, misc punctuation 
d.3 * x                           # expressions  

Стиль

Соглашения PEP8 наложат мягкое ограничение на именование атрибутов:

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

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

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

Б. Правило прецедента для методов и имен переменных :

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

d.Firstname
d.Country

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


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

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


1

Вы можете использовать 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

1
Это хорошая форма, чтобы поместить .ideaи любые пользовательские или сгенерированные IDE файлы в ваш .gitignore.
DeusXMachina

1

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

def make_funcdict(d=None, **kwargs)
    def funcdict(d=None, **kwargs):
        if d is not None:
            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'}

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


0

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

NeoBunch

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

Ссылка на NeoBunch 404
DeusXMachina

0

Самый простой способ - определить класс, назовем его Namespace. который использует объект dict .update () на dict. Тогда дикт будет рассматриваться как объект.

class Namespace(object):
    '''
    helps referencing object in a dictionary as dict.key instead of dict['key']
    '''
    def __init__(self, adict):
        self.__dict__.update(adict)



Person = Namespace({'name': 'ahmed',
                     'age': 30}) #--> added for edge_cls


print(Person.name)
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.