Як використовувати крапкові позначення для dict у python?


81

Я дуже новачок у python, і я хотів би зробити .позначення для доступу до значень a dict.

Скажімо, у мене testтаке:

>>> test = dict()
>>> test['name'] = 'value'
>>> print(test['name'])
value

Але я хотів би зробити, test.nameщоб дістати value. Фактично я зробив це, перевизначивши __getattr__метод у своєму класі так:

class JuspayObject:

    def __init__(self,response):
        self.__dict__['_response'] = response

    def __getattr__(self,key): 
        try:
            return self._response[key]
        except KeyError,err:
            sys.stderr.write('Sorry no key matches')

і це працює! коли я роблю:

test.name // I get value.

Але проблема полягає в тому, що, коли я просто друкую testсамостійно, я отримую помилку як:

'Sorry no key matches'

Чому це відбувається?


Вам потрібно зателефонувати до суперкласу getattr, коли ви запитуєте атрибут, якого немає у вашому дикті.
Девід Хеффернан

@DavidHeffernan Чи має такий клас старого стилю, як приклад OP, навіть суперклас?
Айя,

@Aya Не маю ідеї. Якщо ні, використовуйте новий клас стилю. Хто все одно використовує класи старого стилю?
Девід Хеффернан,

1
@DavidHeffernan Ну, у стандартній бібліотеці Python ще є досить багато класів старого стилю, наприклад cgi.py.
Айя,

Відповіді:


147

Ця функціональність вже існує у стандартних бібліотеках , тому я рекомендую вам просто використовувати їх клас.

>>> from types import SimpleNamespace
>>> d = {'key1': 'value1', 'key2': 'value2'}
>>> n = SimpleNamespace(**d)
>>> print(n)
namespace(key1='value1', key2='value2')
>>> n.key2
'value2'

Додавання, зміна та видалення значень досягається за допомогою регулярного доступу до атрибутів, тобто ви можете використовувати оператори типу n.key = valі del n.key.

Щоб знову повернутися до дикту:

>>> vars(n)
{'key1': 'value1', 'key2': 'value2'}

Ключі у вашому дикті повинні мати ідентифікатори рядків, щоб доступ до атрибутів працював належним чином.

Простий простір імен було додано в Python 3.3. Для старих версій мови argparse.Namespaceмає подібну поведінку.


2
Тканина також має приємну мінімальну реалізацію , яку я також розмістив тут .
Дейв,

5
Було б непогано, якби ви могли повторити це .. тобто доступmyobj.subdict.subdict
Пітікос

2
@Pithikos Існує стороння бібліотека, яка надає цю функціональність, виплачуючи python-box .
wim

@eadmaster Чи читали ви два коментарі безпосередньо над вашим?
wim

43

Я припускаю, що вам комфортно в Javascript і ви хочете запозичити такий синтаксис ... Я можу сказати вам на власному досвіді, що це не найкраща ідея.

Це справді виглядає менш багатослівно і акуратно; але в перспективі це просто неясно. Дикти - це дикти, і намагання змусити їх поводитися як об’єкти з атрибутами, ймовірно, призведе до (поганих) сюрпризів.

Якщо вам потрібно маніпулювати полями об’єкта так, ніби вони є словником, ви завжди можете вдатися до використання внутрішнього __dict__атрибута, коли він вам потрібен, і тоді чітко буде зрозуміло, що ви робите. Або використовуйте, getattr(obj, 'key')щоб врахувати також структуру успадкування та атрибути класу.

Але, читаючи ваш приклад, здається, що ви намагаєтеся щось інше ... Оскільки оператор крапок вже буде шукати в __dict__атрибуті без зайвого коду.


11
Приклад поганого сюрпризу: якщо у вас є ключ, який, здається, є ключовим словом python, наприклад рядок 'for', тоді доступ до атрибутів не вдається, і немає елегантного способу правильно розглянути цю справу. Вся ідея принципово порушена з самого початку.
wim

4
З іншого боку, автозавершення робить більш ефективним, менш схильним до помилок код. Це буде чудово працювати, якщо структуру дикту можна визначити заздалегідь.
roundar

8

Чи можете ви використовувати названий кортеж?

from collections import namedtuple
Test = namedtuple('Test', 'name foo bar')
my_test = Test('value', 'foo_val', 'bar_val')
print(my_test)
print(my_test.name)


2
Це цікавий спосіб отримати доступ до структури даних за допомогою пунктирних позначень, але він, здається, не особливо сумісний з JSON або dict. Під обкладинками є бібліотеки, в яких використовуються названі кортежі, які забезпечують підтримку JSON та dict. Спробуйте github.com/dsc/bunch або github.com/kennknowles/python-jsonpath-rw
MarkHu

для python> = 3.6, розглянемоfrom typing import NamedTuple
user9074332

6

На додаток до цієї відповіді можна додати підтримку і для вкладених диктів:

from types import SimpleNamespace

class NestedNamespace(SimpleNamespace):
    def __init__(self, dictionary, **kwargs):
        super().__init__(**kwargs)
        for key, value in dictionary.items():
            if isinstance(value, dict):
                self.__setattr__(key, NestedNamespace(value))
            else:
                self.__setattr__(key, value)

nested_namespace = NestedNamespace({
    'parent': {
        'child': {
            'grandchild': 'value'
        }
    },
    'normal_key': 'normal value',
})


print(nested_namespace.parent.child.grandchild)  # value
print(nested_namespace.normal_key)  # normal value

Зауважте, що це не підтримує крапкові позначення для диктовок, які знаходяться десь усередині, наприклад, списків.


5

__getattr__використовується як резервний варіант, коли всі інші правила пошуку атрибутів вийшли з ладу. Коли ви намагаєтесь "надрукувати" ваш об'єкт, Python шукає __repr__метод, і оскільки ви не реалізуєте його у своєму класі, він закінчується викликом __getattr__(так, у методах Python це також атрибути). Ви не повинні припускати, за допомогою якого ключа буде викликаний getattr , і, що найважливіше, __getattr__повинен викликати AttributeError, якщо він не може вирішити проблему key.

Як допоміжна примітка: не використовуйте self.__dict__для звичайного доступу до атрибутів, просто використовуйте простий запис атрибута:

class JuspayObject:

    def __init__(self,response):
        # don't use self.__dict__ here
        self._response = response

    def __getattr__(self,key):
        try:
            return self._response[key]
        except KeyError,err:
            raise AttributeError(key)

Тепер, якщо у вашого класу немає іншої відповідальності (а ваша версія Python>> 2.6 і вам не потрібно підтримувати старіші версії), ви можете просто використати namedtuple: http://docs.python.org/2/library/ collection.html # collection.namedtuple


2

Ви повинні бути обережними при використанні __getattr__, оскільки він використовується для великої кількості вбудованих функціональних можливостей Python.

Спробуйте щось подібне ...

class JuspayObject:

    def __init__(self,response):
        self.__dict__['_response'] = response

    def __getattr__(self, key):
        # First, try to return from _response
        try:
            return self.__dict__['_response'][key]
        except KeyError:
            pass
        # If that fails, return default behavior so we don't break Python
        try:
            return self.__dict__[key]
        except KeyError:
            raise AttributeError, key

>>> j = JuspayObject({'foo': 'bar'})
>>> j.foo
'bar'
>>> j
<__main__.JuspayObject instance at 0x7fbdd55965f0>

1
__getattr__використовується лише як резервний варіант, якщо жодне інше правило пошуку не відповідає, тому немає необхідності шукати self.__dict__[key], це вже було зроблено, якщо __getattr__викликано. Достатньо просто підняти, AttributeErrorякщо пошук на self._response ['key'] не вдався.
bruno desthuilliers

@brunodesthuilliers Схоже, ти маєш рацію. Дивно. Можливо, раніше це було потрібно лише за часів Python v1.5.
Айя,

-1

Додайте __repr__()метод до класу, щоб ви могли налаштувати текст для відображення

print text

Дізнайтеся більше тут: https://web.archive.org/web/20121022015531/http://diveintopython.net/object_oriented_framework/special_class_methods2.html


Хоча це технічно вирішило б конкретний випадок, згаданий у питанні, хакерство вбудованої поведінки для виправлення інших хаків вбудованої поведінки є об'єктивно поганим кодом.
vdwees
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.