Доступ к клавишам 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}
Ответ 5
Caveat emptor: По некоторым причинам подобные классы, похоже, разбивают пакет многопроцессорности. Я просто боролся с этой ошибкой некоторое время, прежде чем найти это SO:
Поиск исключения в многопроцессорности python
Ответ 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