Зміст словника для деструктуризації та прив’язки


84

Я намагаюся "деструктурувати" словник та пов'язати значення з іменами змінних після його ключів. Щось на зразок

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

Але оскільки словники не впорядковані, немає жодної гарантії, що params.values()повернуть значення у порядку (a, b). Чи є хороший спосіб це зробити?


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

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

8
@Andy Я люблю деструктуризацію об'єктів у JS. Який чистий, простий і читабельний спосіб витягти деякі ключі з дикту. Я прийшов сюди з надією знайти щось подібне в Python.
Ротареті,

2
Мені також подобається деструктуризація об'єктів ES6, але я боюся, що вона не може працювати в Python з тієї ж причини, що об'єкт Map ES6 не підтримує деструктуризацію. Клавіші - це не просто рядки в ES6 Map та Python dict. Крім того, хоча я люблю стиль "вищипування" деструктуризації об'єктів у ES6, стиль призначення не є простим. Що тут відбувається? let {a: waffles} = params. Щоб зрозуміти це, потрібно кілька секунд, навіть якщо ви до цього звикли.
Джон Крістофер Джонс,

1
@ naught101 Ситуаційно корисний з неприємними сюрпризами для компромісів. Для користувачів: У Python будь-який об’єкт може надати власні методи str / repr. Може бути навіть спокусливо зробити це для трохи складних ключових об'єктів (наприклад, іменованих кортежів) для полегшення серіалізації JSON. Тепер ти залишився чухати голову, чому ти не можеш зруйнувати ключ за іменем. Крім того, чому це працює для елементів, але не для атрибутів? Багато бібліотек віддають перевагу атрибутам. Для реалізаторів ця функція ES6 плутає символи (зв’язувані імена) та рядки: розумно в JavaScript, але Python має набагато багатші ідеї. Крім того, це просто виглядало б негарно.
Джон Крістофер Джонс,

Відповіді:


10

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


@Stephen вам пощастило, бо OrderedDicts перенесено на python 2.7 з python 3.1
joaquin

Я з нетерпінням чекаю цього...! Думка, що для мене завжди було стиканням для Python (що замовлені словники не завантажувались іншими батареями)
hatmatrix

4
На даний час у версії 3.5+ усі словники впорядковані. Це ще не "гарантовано", тобто може змінитися.
Чарльз Мерріам,

4
Це гарантовано від 3.6+.
naught101

118
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
Але ви двічі набираєте а та 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.
Джон Крістофер Джонс,

28

Одним із способів зробити це з меншим повторенням, ніж пропозиція Йохена, є допоміжна функція. Це надає гнучкість для переліку імен змінних у будь-якому порядку і лише деструктурує підмножину того, що є в dict:

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

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

Крім того, замість Joaquin's OrderedDict ви можете відсортувати ключі та отримати значення. Єдине, що вам потрібно, - вам потрібно вказати імена змінних в алфавітному порядку і деструктурувати все, що є в 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змінну, ви можете також використовувати звичайний синтаксис функції з def.
Артур Такка

ви не можете зробити це як JS, де це буде const {a, b} = {a: 1, b: 2}
PirateApp

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

16

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 02

10

Ось ще один спосіб зробити це подібно до того, як працює призначення деструктуризації в JS:

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

Те, що ми зробили, було розпакувати словник params за ключовими значеннями (використовуючи **) (як у відповіді Йохена ), потім ми взяли ці значення в лямбда-підпис і призначили їх відповідно до назви ключа - і ось бонус - ми також отримайте словник того, чого немає в підписі лямбди, тож якщо у вас було:

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

Після того, як лямбда буде застосована, змінна rest тепер буде містити: {'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

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)

(Зверніть увагу на порядок предметів у дикті)


0

Оскільки словники гарантовано зберігають порядок вставки в Python> = 3.7 , це означає, що сьогодні це просто безпечно та ідіоматично:

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

Вихід:

1
2

2
Чудово! З моєї посади їм пройшло лише 9 років. :)
hatmatrix

1
Проблема в тому, що ви не можете цього зробити b, a = params.values(), оскільки він використовує порядок, а не імена.
Корман

1
@ruohola В цьому проблема цього рішення. Це спирається на порядок словників імен, а не на самі імена, саме тому я підтримав це.
Корман

1
І це робить це поганим рішенням, якщо воно покладається на порядок клавіш. itemgetterце найбільш пітонічний спосіб зробити це. Це не буде працювати на Python 3.6 або нижче, і оскільки воно покладається на порядок, спочатку це може заплутати.
Корман,

-1

Я не знаю, чи це хороший стиль, але

locals().update(params)

зробить трюк. Тоді у вас є a, bі все, що було у вашому paramsдикті, доступне як відповідні локальні змінні.


2
Зверніть увагу, що це може бути великою проблемою безпеки, якщо словник "params" надається користувачем будь-яким способом і не фільтрується належним чином.
Jacek Konieczny

9
Процитувати docs.python.org/library/functions.html#locals : Примітка: Зміст цього словника не повинно бути змінено; зміни можуть не впливати на значення локальних та вільних змінних, що використовуються інтерпретатором.
Jochen Ritzel

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