Деструктуризация-привязка содержимого словаря


85

Я пытаюсь «разрушить» словарь и связать значения с именами переменных после их ключей. Что-то вроде

params = {'a':1,'b':2}
a,b = params.values()

Но поскольку словари не упорядочены, нет гарантии, что params.values()значения будут возвращены в порядке (a, b). Есть хороший способ сделать это?


3
Ленивый? Может быть ... но, конечно, для иллюстрации я показал простейший случай. В идеале я хотел бы иметь подобное для x в params.items: eval ('% s =% f'% x), но я полагаю, что eval () не допускает присваивания.
hatmatrix 02

7
@JochenRitzel Я уверен , что большинство пользователей ES6 (JavaScript) нравится новый синтаксис объект деструктурирующего: let {a, b} = params. Он улучшает читаемость и полностью соответствует тому дзен, о котором вы хотите поговорить.
Энди

8
@Andy Мне нравится деструктуризация объектов в JS. Какой чистый, простой и читаемый способ извлечь некоторые ключи из dict. Я приехал сюда в надежде найти что-то подобное в Python.
Rotareti

2
Мне также нравится деструктуризация объекта ES6, но я боюсь, что он не может работать в Python по той же причине, по которой объект Map ES6 не поддерживает деструктуризацию. Ключи - это не просто строки в ES6 Map и Python dict. Кроме того, хотя мне нравится «выщипывающий» стиль деструктуризации объектов в ES6, стиль присваивания не прост. Что тут происходит? let {a: waffles} = params. На это уходит несколько секунд, даже если вы к этому привыкли.
Джон Кристофер Джонс

1
@ naught101 Ситуационно полезен, но есть неприятные сюрпризы в обмен на компромисс. Для пользователей: в Python любой объект может предоставлять свои собственные методы str / repr. Может возникнуть соблазн сделать это для слегка сложных ключевых объектов (например, именованных кортежей) для упрощения сериализации JSON. Теперь вы остаетесь ломать голову, почему нельзя разрушить ключ по имени. Кроме того, почему это работает для items, но не для attrs? Многие библиотеки предпочитают attrs. Для разработчиков эта функция ES6 путает символы (связываемые имена) и строки: разумно в JavaScript, но в Python есть гораздо более богатые идеи. Кроме того, это выглядело бы просто некрасиво.
Джон Кристофер Джонс

Ответы:


10

Если вы опасаетесь проблем, связанных с использованием словаря местных жителей, и предпочитаете следовать своей исходной стратегии, Ordered Dictionaries из коллекций python 2.7 и 3.1. OrderedDicts позволяет восстанавливать элементы словаря в том порядке, в котором они были впервые вставлены


@Stephen, вам повезло, потому что OrderedDicts был перенесен на python 2.7 с python 3.1
Хоакин 02

Я с нетерпением жду этого ...! Думал, что для меня это всегда было камнем преткновения для Python (заказанные словари не были загружены с другими батареями)
hatmatrix

4
На данный момент в 3.5+ все словари заказаны. Это еще не «гарантировано», то есть может измениться.
Чарльз Мерриам

4
Гарантия от 3.6+.
ничто101

123
from operator import itemgetter

params = {'a': 1, 'b': 2}

a, b = itemgetter('a', 'b')(params)

Вместо сложных лямбда-функций или понимания словаря можно также использовать встроенную библиотеку.


9
Вероятно, это должен быть принятый ответ, поскольку это самый питонический способ сделать это. Вы даже можете расширить ответ, чтобы использовать attrgetterтот же стандартный библиотечный модуль, который работает с атрибутами объекта ( obj.a). Это серьезное отличие от JavaScript, где obj.a === obj["a"].
Джон Кристофер Джонс

Если ключ не существует в словаре, KeyErrorбудет
возбуждено

2
Но теперь вы дважды набираете a и b в заявлении о деструктуризации
Отто

1
@JohnChristopherJones Мне это не кажется очень естественным, использование существующего материала не означает, что это делает его понятным. Я сомневаюсь, что многие люди сразу поймут реальный код. С другой стороны, предложенный a, b = [d[k] for k in ('a','b')]вариант более естественен / читабелен (форма более распространена). Это все еще интересный ответ, но это не самое простое решение.
cglacet

@cglacet Я думаю, это зависит от того, сколько раз вам придется это читать / реализовывать. Если вы обычно выбираете одни и те же 3 ключа, то проще get_id = itemgetter(KEYS)использовать что-то вроде того, что позже serial, code, ts = get_id(document). По общему признанию, вы должны хорошо разбираться в функциях высшего порядка, но Python, как правило, очень комфортно с ними. Например, см. Документацию для декораторов вроде @contextmanager.
Джон Кристофер Джонс,

29

Один из способов сделать это с меньшим количеством повторений, чем предлагает Йохен, - использовать вспомогательную функцию. Это дает возможность перечислять имена переменных в любом порядке и разрушать только подмножество того, что находится в dict:

pluck = lambda dict, *args: (dict[arg] for arg in args)

things = {'blah': 'bleh', 'foo': 'bar'}
foo, blah = pluck(things, 'foo', 'blah')

Кроме того, вместо OrderedDict joaquin вы можете отсортировать ключи и получить значения. Единственная загвоздка в том, что вам нужно указать имена переменных в алфавитном порядке и разрушить все в dict:

sorted_vals = lambda dict: (t[1] for t in sorted(dict.items()))

things = {'foo': 'bar', 'blah': 'bleh'}
blah, foo = sorted_vals(things)

4
Это небольшая придирка, но если вы собираетесь присвоить lambdaпеременной a, вы также можете использовать обычный синтаксис функции с def.
Артур Такка

проголосовали за, разве вы не можете сделать это как JS, где было бы const {a, b} = {a: 1, b: 2}
PirateApp

1
Вы реализовали itemgetterиз operatorстандартной библиотеки. :)
Джон Кристофер Джонс

17

Python может только «деструктурировать» последовательности, но не словари. Итак, чтобы написать то, что вы хотите, вам нужно будет сопоставить необходимые записи с правильной последовательностью. Что касается меня, самое близкое совпадение, которое я смог найти, - это (не очень сексуально):

a,b = [d[k] for k in ('a','b')]

Это также работает с генераторами:

a,b = (d[k] for k in ('a','b'))

Вот полный пример:

>>> d = dict(a=1,b=2,c=3)
>>> d
{'a': 1, 'c': 3, 'b': 2}
>>> a, b = [d[k] for k in ('a','b')]
>>> a
1
>>> b
2
>>> a, b = (d[k] for k in ('a','b'))
>>> a
1
>>> b
2

10

Может быть, вы действительно хотите сделать что-то подобное?

def some_func(a, b):
  print a,b

params = {'a':1,'b':2}

some_func(**params) # equiv to some_func(a=1, b=2)

Спасибо, но не то ... Я деструктурирую внутри функции
hatmatrix

10

Вот еще один способ сделать это аналогично тому, как деструктурирующее присваивание работает в JS:

params = {'b': 2, 'a': 1}
a, b, rest = (lambda a, b, **rest: (a, b, rest))(**params)

Что мы сделали, так это распаковали словарь параметров в ключевые значения (используя **) (как в ответе Йохена ), затем мы взяли эти значения в лямбда-сигнатуре и назначили их в соответствии с именем ключа - и вот бонус - мы также получите словарь всего, чего нет в сигнатуре лямбда, поэтому, если у вас есть:

params = {'b': 2, 'a': 1, 'c': 3}
a, b, rest = (lambda a, b, **rest: (a, b, rest))(**params)

После применения лямбда остальная переменная теперь будет содержать: {'c': 3}

Полезно для исключения ненужных ключей из словаря.

Надеюсь это поможет.


Интересно, с другой стороны, мне кажется, что в функции было бы лучше. Вы, вероятно, будете использовать это несколько раз, и таким образом у вас тоже будет имя. (когда я говорю «функция», я имею в виду, а не лямбда-функцию).
cglacet

3

попробуй это

d = {'a':'Apple', 'b':'Banana','c':'Carrot'}
a,b,c = [d[k] for k in ('a', 'b','c')]

результат:

a == 'Apple'
b == 'Banana'
c == 'Carrot'

3

Предупреждение 1: как указано в документации, это не гарантируется для всех реализаций Python:

Детали реализации CPython: эта функция полагается на поддержку фрейма стека Python в интерпретаторе, которая не гарантируется во всех реализациях Python. При запуске в реализации без поддержки фрейма стека Python эта функция возвращает None.

Предупреждение 2: эта функция делает код короче, но, вероятно, противоречит философии Python о максимальной ясности. Более того, он не решает проблемы, указанные Джоном Кристофером Джонсом в комментариях, хотя вы можете создать аналогичную функцию, которая работает с атрибутами вместо ключей. Это просто демонстрация того, что вы можете это сделать, если действительно захотите!

def destructure(dict_):
    if not isinstance(dict_, dict):
        raise TypeError(f"{dict_} is not a dict")
    # the parent frame will contain the information about
    # the current line
    parent_frame = inspect.currentframe().f_back

    # so we extract that line (by default the code context
    # only contains the current line)
    (line,) = inspect.getframeinfo(parent_frame).code_context

    # "hello, key = destructure(my_dict)"
    # -> ("hello, key ", "=", " destructure(my_dict)")
    lvalues, _equals, _rvalue = line.strip().partition("=")

    # -> ["hello", "key"]
    keys = [s.strip() for s in lvalues.split(",") if s.strip()]

    if missing := [key for key in keys if key not in dict_]:
        raise KeyError(*missing)

    for key in keys:
        yield dict_[key]
In [5]: my_dict = {"hello": "world", "123": "456", "key": "value"}                                                                                                           

In [6]: hello, key = destructure(my_dict)                                                                                                                                    

In [7]: hello                                                                                                                                                                
Out[7]: 'world'

In [8]: key                                                                                                                                                                  
Out[8]: 'value'

Это решение позволяет вам выбирать некоторые ключи, а не все, как в JavaScript. Это также безопасно для пользовательских словарей


1

Что ж, если вы хотите, чтобы это было в классе, вы всегда можете сделать это:

class AttributeDict(dict):
    def __init__(self, *args, **kwargs):
        super(AttributeDict, self).__init__(*args, **kwargs)
        self.__dict__.update(self)

d = AttributeDict(a=1, b=2)

Ницца. Спасибо, но, похоже, способ изменить синтаксис вызова с d ['a'] на da? И, возможно, добавление методов, которые имеют неявный доступ к этим параметрам ...
hatmatrix 02

0

Основываясь на ответе @ShawnFumo, я придумал следующее:

def destruct(dict): return (t[1] for t in sorted(dict.items()))

d = {'b': 'Banana', 'c': 'Carrot', 'a': 'Apple' }
a, b, c = destruct(d)

(Обратите внимание на порядок элементов в dict)


0

Поскольку словари гарантированно сохранят свой порядок вставки в Python> = 3.7 , это означает, что сейчас это совершенно безопасно и идиоматично:

params = {'a': 1, 'b': 2}
a, b = params.values()
print(a)
print(b)

Вывод:

1
2

2
Большой! С моего поста прошло всего 9 лет. :)
hatmatrix 08

1
Проблема в том, что вы не можете этого сделать b, a = params.values(), поскольку он использует порядок, а не имена.
Corman

1
@ruohola Это проблема с этим решением. Это зависит от порядка словарей для имен, а не от самих имен, поэтому я проголосовал против этого.
Corman

1
И это делает его плохим решением, если оно зависит от порядка ключей. itemgetter- самый питонический способ сделать это. Это не будет работать на Python 3.6 или ниже, и, поскольку он зависит от порядка, поначалу это может сбивать с толку.
Corman

-1

Не знаю, хороший ли это стиль, но

locals().update(params)

сделает свое дело. Затем у вас есть a, bи все , что было в вашем paramsdict, доступно как соответствующие локальные переменные.


2
Обратите внимание, что это может быть большой проблемой безопасности, если словарь 'params' предоставлен пользователем каким-либо образом и не отфильтрован должным образом.
Jacek Konieczny

9
Чтобы процитировать docs.python.org/library/functions.html#locals : Примечание. Не следует изменять содержимое этого словаря; изменения не могут влиять на значения локальных и свободных переменных, используемых интерпретатором.
Йохен Ритцель

4
Усвоил урок. Спасибо, ребята.
Johannes Charra
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.