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


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 не забезпечує цю функціональність поза коробкою. Якими були б застереження та підводні камені доступу до клавіш Dict таким чином?


16
Якщо ви отримуєте доступ до твердо кодованих ключів із обмеженого набору фіксованого розміру скрізь, можливо, вам буде краще створити об'єкти, які містять їх. collections.namedtupleдуже корисний для цього.

6
stackoverflow.com/questions/3031219/… має подібне рішення, але йде на крок далі
keflavich

1
Знайдено модуль для цього на веб- сайті github.com/bcj/AttrDict . Я не знаю, як вона порівнюється з рішеннями тут і в пов'язаних з ними питаннях.
matt wilkie

Я також використовував подібні хаки, зараз я використовуюeasydict.EasyDict
muon

Більше способів доступу до членів словника із знаком "." : 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
  • Пілінт йде з бананами E1123(unexpected-keyword-arg)іE1103(maybe-no-member)
  • Для непосвячених це здається чистою магією.

Коротке пояснення, як це працює

  • Усі об'єкти python внутрішньо зберігають свої атрибути у словнику, який названий __dict__.
  • Немає вимоги, що внутрішній словник __dict__повинен бути "просто простим диктатом", тому ми можемо призначити будь-який підклас dict()внутрішнього словника.
  • У нашому випадку ми просто присвоюємо AttrDict()екземпляр, який ми створюємо (як ми знаходимось __init__).
  • Зателефонувавши super()за __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
Як ви думаєте, витік пам'яті відбудеться з таким простим об'єктом, як: >>> клас MyD (об’єкт): ... def init __ (self, d): ... self .__ dict = d
Rafe

Викликає витік навіть у 2,7
пі.

1
Зробіть це <= 2.7.3, оскільки це я використовую.
пі.

1
У примітках до випуску 2.7.4 вони згадуються про фіксовану (не раніше).
Роберт Сімер

1
@viveksinghggits тільки тому, що ви отримуєте доступ до речей через ., ви не можете порушити правила мови :) І я не хотів AttrDictби автоматично перетворювати місця, що містять простір, у щось інше.
Юрик

125

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


1
@Izkata так. Смішно про SE, що зазвичай є "перше питання", тобто. назва та "нижнє запитання", можливо тому, що SE не любить чути "заголовок каже все"; "застереження" є найнижчим тут.
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). В інших випадках, коли я спокушаюсь створити динамічний клас і нанести на нього деякі атрибути з естетичних міркувань, я продовжую використовувати вислів для послідовності з метою підвищення читабельності.

Я впевнений, що ОП давно вирішив це на його задоволення, але якщо він все-таки хоче цю функціональність, то я пропоную йому завантажити один з пакетів з pypi, який це забезпечує:

  • Пучок - той, з ким я більше знайомий. Підкласdict, тому у вас є весь цей функціонал.
  • AttrDict також виглядає, що це теж непогано, але я не так знайомий з ним і не переглянув джерело так детально, як у мене букет .
  • Наркоманія активно підтримується і надає доступ, схожий на attr тощо.
  • Як зазначається в коментарях Ротареті, Бунк був застарілим, але є активна вилка під назвою 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
Купа застаріла, але є її активна вилка: github.com/Infinidat/munch
Rotareti

@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
ПЛЮС 1 для посилання на стандартну бібліотеку, яка стосується першого коментаря ОП.
Гордон Бін

4
Для цього випадку Python включає більш швидкий клас (реалізований на C): types.SimpleNamespace docs.python.org/dev/library/types.html#types.SimpleNamespace
Nuno André

18

Що робити, якщо ви хочете ключ, який був методом, таким як __eq__або __getattr__?

І ви б не змогли мати запис, який не починався з літери, тому використання 0343853ключа не працює.

А що робити, якщо ви не хочете використовувати рядок?


Дійсно, або, наприклад, інші об'єкти як ключі. Однак я б класифікував цю помилку як «очікувану поведінку» - з моїм питанням я більше прагнув до несподіваного.
Izz ad-Din Ruhulessin

pickle.dumpвикористовує__getstate__
Cees Timmerman

12

кортежі можна використовувати клавіші dict. Як би ви отримали доступ до кортежу у своїй конструкції?

Також nametuple - це зручна структура, яка може надавати значення через доступ до атрибутів.


7
Недолік названих пар - це те, що вони незмінні.
Izz ad-Din Ruhulessin

10
Дехто сказав, що бути непорушним - це не помилка, а особливість кортежів.
автор авторів

9

Як щодо Продію , маленького класу 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+ з-за анотацій типів
Ramazan Polat

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 як attr" в екосистемі python. Як зазначають декілька коментаторів, це, мабуть, не те, що ви хочете скочувати з нуля , оскільки є кілька підводних каменів та підошов, деякі з них дуже тонкі. Також я б не рекомендував використовуватиNamespace як базовий клас, я пішов по цій дорозі, це не дуже.

На щастя, існує декілька пакетів з відкритим кодом, що надають цю функціональність, готові встановити файли! На жаль, є кілька пакетів. Ось конспект станом на грудень 2019 року.

Учасники (останні останні зобов’язання керувати | #commissions | #contribs | покриття%):

Більше не підтримується або недостатньо підтримується:

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

У мене немає собаки на цій гонці (поки що!), До того ж я прокатав свій власний код dict / attr і витратив багато часу, тому що я не знав про всі ці варіанти :). В майбутньому я можу зробити свій внесок у залежність / жабування, тому що я скоріше побачу один міцний пакет, ніж купу роздроблених. Якщо вони вам подобаються, додавайте! Зокрема, схожий на те, що Munch може використовувати значок codecov, а наркоман може використовувати значок версії python.

профі-наркомани:

  • рекурсивна ініціалізація (foo.abc = 'bar'), диктуючі аргументи стають залежними.

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

  • тіні, typing.Dictякщо тиfrom addict import Dict
  • Немає перевірки ключа. Завдяки дозволу рекурсивного init, якщо ви неправильно написали ключ, ви просто створите новий атрибут, а не KeyError (спасибі AljoSt)

плюси:

  • унікальне називання
  • вбудовані сер / де функції для JSON та YAML

мінуси:

  • жоден рекурсивний init / тільки не може init один attr за один раз

У якому я редактую

Багато місяців тому, коли я використовував текстові редактори, щоб писати 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 може автоматично заповнювати поля. Також підкласифікація ванілі dictозначає, що серіалізація json проста. Я думаю, що наступною еволюцією цієї ідеї буде власний генератор протобуфів, який випромінює ці інтерфейси, і приємний стук - ви отримуєте міжмовні структури даних та IPC через gRPC майже безкоштовно.

Якщо ви все-таки вирішите перейти з attr-dicts, важливо задокументувати, які поля очікуються, для вашого власного (та ваших товаришів) розуму.

Не соромтесь редагувати / оновлювати цю публікацію, щоб вона була останньою!


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

Просто, щоб додати певну різноманітність до відповіді, науковий комплект 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                                                                           
    >>> 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виправлення виправлення для маринування / відсікання "пучків" - якщо не зацікавлено перевірити https://github.com/scikit-learn/scikit-learn/isissue/6196


3

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

Перевага об’єктів класу, ймовірно, грає у визначенні класу та успадкуванні.


3

Я створив це на основі входу з цієї нитки. Мені потрібно використовувати одикт, тому мені довелося перекрити 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)

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

Це хороша швидка та брудна відповідь. Моє єдине зауваження / коментар - це те, що я думаю, що конструктор з назвоюtuple прийме список рядків, тому ваше рішення можна спростити (я думаю) до:namedtuple('Args', list(args.keys()))(**args)
Dan Nguyen

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()дає TypeError: 'int' object is not callable.
bfontaine

@bfontaine Ідея має бути видом, field/attributeа не символом 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

Якими були б застереження та підводні камені доступу до клавіш Dict таким чином?

Як підказує @Henry, одна з причин пунктирного доступу не може бути використана в диктатах - це те, що він обмежує диктувати імена ключів змінними 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

Якщо ім'я аргументу функції стикається із зарезервованим ключовим словом, як правило, краще додати єдиний кінцевий підкреслення ...

B. Правило справи про методи та назви змінних :

Імена змінних дотримуються тієї ж умови, що і назви функцій.

d.Firstname
d.Country

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


Іноді такі проблеми виникають у бібліотеках, як панди , що дозволяє пунктирно отримувати доступ до стовпців 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 робить їх набагато більш читабельними, тому, якщо у вас трапляється використовувати звичайний 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

Посилання на NeoBunch - 404
DeusXMachina

0

Найпростіший спосіб - визначити клас, назвемо його Іменний простір. який використовує об'єкт 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.