it-swarm-ru.tech

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

Мне удобнее обращаться к ключам dict как obj.foo вместо obj['foo'], поэтому я написал этот фрагмент:

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

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

231
Izz ad-Din Ruhulessin

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

class AttrDict(dict):
    def __init__(self, *args, **kwargs):
        super(AttrDict, self).__init__(*args, **kwargs)
        self.__dict__ = self

Некоторые плюсы:

  • Это на самом деле работает!
  • Ни один из методов класса словаря не затенен (например, .keys() работает просто отлично)
  • Атрибуты и элементы всегда синхронизированы
  • Попытка получить доступ к несуществующему ключу в качестве атрибута корректно вызывает AttributeError вместо KeyError

Минусы:

  • Такие методы, как .keys(), будут not работать нормально, если они перезаписываются входящими данными.
  • Вызывает утечку памяти в Python <2.7.4/Python3 <3.2.3
  • Пилинт сходит с ума с 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!"
245
Kimvais

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

118
Hery

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

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

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

67
slacy

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

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

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

Предостережения и подводные камни

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

>>> 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 Cookbook (Ch. 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.

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

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


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

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

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

Хотя я никогда не использовал этот вариант использования (опять же, я склонен использовать вложенную 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}
59
Doug R.

Предостережение: по некоторым причинам такие классы, кажется, нарушают многопроцессорный пакет. Я только что некоторое время боролся с этой ошибкой, прежде чем нашел это SO: Поиск исключения в многопроцессорной обработке Python

19
Ryan

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

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

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

18
The Communist Duck

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

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__
15
lindyblackburn

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

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

11
Senthil Kumaran

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

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

8
tallseth

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

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

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

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

print rec.loss(..)
4
Ben Usman

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

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'
4
gonz

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

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

3
Yurik

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

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

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

3
David W

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

3
user2804865

Позвольте мне опубликовать еще одну реализацию, которая основывается на ответе 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
2
kadee
class AttrDict(dict):

     def __init__(self):
           self.__dict__ = self

if __== '____main__':

     d = AttrDict()
     d['ray'] = 'hope'
     d.Sun = 'shine'  >>> Now we can use this . notation
     print d['ray']
     print d.Sun
1
h_vm

Решение:

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
Bruno Rocha - rochacbruno

Как отметил Дуг, есть пакет 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
1
mizu

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

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

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

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

0
DylanYoung

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

Как предполагает @Henry, одной из причин, по которым точечный доступ может не использоваться в dicts, является то, что он ограничивает имена ключей dict допустимыми переменными 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'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 по имени. Механизмом по умолчанию для разрешения ограничений именования является также обозначение массива - строка в скобках. 

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

0
pylang