Питання Як об'єднати два словника в одному виразі?


У мене є два словника Python, і я хочу написати єдиний вираз, який повертає ці два словники, об'єднані. The update() метод буде тим, що мені потрібно, якщо він поверне його результат, а не модифікує дікт в місці.

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> z = x.update(y)
>>> print(z)
None
>>> x
{'a': 1, 'b': 10, 'c': 11}

Як я можу отримати цей остаточний об'єднаний dict в z, ні x?

(Щоб бути додатковим, останній виграє конфлікт обробки dict.update() це те, що я теж шукаю.)


3222
2017-09-02 07:44


походження




Відповіді:


Як я можу об'єднати два словники Python в одному виразі?

Для словників x і y, z стає об'єднаним словником зі значеннями з y замінивши їх x.

  • У Python 3.5 або вище:

    z = {**x, **y}
    w = {'foo': 'bar', 'baz': 'qux', **y}  # merge a dict with literal values
    
  • У Python 2 (або 3.4 або нижче) напишіть функцію:

    def merge_two_dicts(x, y):
        z = x.copy()   # start with x's keys and values
        z.update(y)    # modifies z with y's keys and values & returns None
        return z
    

    і

    z = merge_two_dicts(x, y)
    

Пояснення

Скажімо, у вас є два диктування, і ви хочете об'єднати їх у новий дикт, не змінюючи оригінальних думок:

x = {'a': 1, 'b': 2}
y = {'b': 3, 'c': 4}

Очікуваний результат - отримати новий словник (z), коли значення злиті, а значення другого диктування перезаписують їх з першого.

>>> z
{'a': 1, 'b': 3, 'c': 4}

Новий синтаксис для цього, запропонований в PEP 448 і доступно з Python 3.5, є

z = {**x, **y}

І це дійсно єдиний вираз. Тепер він показується, як реалізовано в графік випуску для 3.5, PEP 478, і зараз він пробився Що нового в Python 3.5 документ

Однак, оскільки багато організацій все ще знаходяться на Python 2, ви можете зробити це на зворотному шляху. Класично Pythonic шлях, доступний в Python 2 і Python 3.0-3.4, це зробити як двоетапний процес:

z = x.copy()
z.update(y) # which returns None since it mutates z

В обох підходах y прийде другий, і його значення замінять xросійські цінності, таким чином 'b' вкажемо на 3 в нашому кінцевому результаті.

Ще не на Python 3.5, але хочете a одиночне вираження

Якщо ви ще не в Python 3.5, або вам потрібно написати зворотний код, і ви хочете це в a одиночне вираження, найбільш показовим, тоді як правильний підхід - поставити його в функцію:

def merge_two_dicts(x, y):
    """Given two dicts, merge them into a new dict as a shallow copy."""
    z = x.copy()
    z.update(y)
    return z

і тоді ви маєте один вираз:

z = merge_two_dicts(x, y)

Ви також можете створити функцію для злиття невизначеної кількості дуктів, від нуля до дуже великого числа:

def merge_dicts(*dict_args):
    """
    Given any number of dicts, shallow copy and merge into a new dict,
    precedence goes to key value pairs in latter dicts.
    """
    result = {}
    for dictionary in dict_args:
        result.update(dictionary)
    return result

Ця функція буде працювати в Python 2 і 3 для всіх dicts. наприклад, Дані диктату a до g:

z = merge_dicts(a, b, c, d, e, f, g) 

і ключові значення пар в g буде мати перевагу над дітьми a до f, і так далі.

Критики інших відповідей

Не використовуйте те, що ви бачите в раніше прийнятій відповіді:

z = dict(x.items() + y.items())

У Python 2 ви створюєте два списки в пам'яті для кожного dict, створюйте третій список у пам'яті довжиною, рівною довжині перших двох разом, а потім відкиньте всі три списки, щоб створити dict. У Python 3 це не вдасться тому що ви додаєте два dict_items об'єкти разом, а не два списки -

>>> c = dict(a.items() + b.items())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'dict_items' and 'dict_items'

і вам доведеться явно створити їх як списки, наприклад, z = dict(list(x.items()) + list(y.items())). Це трата ресурсів та обчислювальних потужностей.

Точно так само, приймаючи союз Росії items()в Python 3 (viewitems() у Python 2.7) також вийде збій, коли значення є нештатними об'єктами (наприклад, списки). Навіть якщо ваші значення є хешувальними, оскільки набори семантично неупорядковані, поведінка невизначена щодо переваги. Тому не робіть цього:

>>> c = dict(a.items() | b.items())

Цей приклад демонструє, що відбувається, коли значення неактивні.

>>> x = {'a': []}
>>> y = {'b': []}
>>> dict(x.items() | y.items())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'

Ось приклад, де y має бути пріоритет, але замість цього значення x зберігається внаслідок довільного порядку наборів:

>>> x = {'a': 2}
>>> y = {'a': 1}
>>> dict(x.items() | y.items())
{'a': 2}

Інший хак не слід використовувати:

z = dict(x, **y)

Це використовує dict конструктор і є дуже швидким та ефективним (навіть трохи більше, ніж наш двоступеневий процес), але якщо ви точно не знаєте, що відбувається тут (тобто другий dict передано як аргументи ключових слів для конструктора dict) це важко читати, це не призначене використання, і тому це не Pythonic.

Ось приклад використання буття виправлено в джанго.

Dicts призначені для прийому хеш-пам'ятних ключів (наприклад, замків або кортежів), але цей метод не працює в Python 3, коли ключі не є рядками.

>>> c = dict(a, **b)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: keyword arguments must be strings

Від список адресатів, Гвідо ван Россум, творець мови, писав:

Я в порядку   декларування dict ({}, ** {1: 3}) незаконний, адже зрештою це зловживання   механізм **.

і

Очевидно, dict (x, ** y) йде навколо, як "крутий хак" для "дзвінка"   x.update (y) і повернення x ". Особисто я вважаю це більш нічим ніж   круто

Це моє розуміння (а також розуміння творець мови), що призначено для використання dict(**y) це для створення думок для цілей читання, наприклад:

dict(a=1, b=10, c=11)

замість

{'a': 1, 'b': 10, 'c': 11}

Відповідь на коментарі

Незважаючи на те, що говорить Гвідо dict(x, **y) відповідає специфікації dict, яка до речі. працює як для Python 2, так і для 3-го. Той факт, що це працює тільки для рядкових ключів, є прямим наслідком того, як працюють параметри ключових слів, а не короткий набір dict. Також не використовує оператора ** в цьому місці зловживання механізмом, фактично ** було розроблено саме для того, щоб пройти dicts як ключові слова.

Знову ж таки, це не працює для 3, коли ключі є без strings. Неявний контракт на виклик полягає в тому, що прості імена приймають звичайні дані, тоді як користувачі повинні передавати аргументи ключових слів, які є рядками. Всі інші викликові вимоги виконували його. dict порушив цю послідовність в Python 2:

>>> foo(**{('a', 'b'): None})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: foo() keywords must be strings
>>> dict(**{('a', 'b'): None})
{('a', 'b'): None}

Ця непослідовність була поганою внаслідок інших реалізацій Python (Pypy, Jython, IronPython). Таким чином, це було зафіксовано в Python 3, оскільки це використання може стати ламаною зміною.

Я підкоряюсь вам, що злочинна некомпетентність навмисно писати код, який працює тільки в одній версії мови або працює лише з певними довільними обмеженнями.

Інший коментар:

dict(x.items() + y.items()) як і раніше, є найбільш читабельним рішенням для Python 2. Кількість читабельності.

Моя відповідь: merge_two_dicts(x, y) насправді здається набагато зрозумілішим для мене, якщо ми насправді стурбовані читаемостью. І це не є сумісним, оскільки Python 2 все більше застаріє.

Менше виконавців, але правильні оголошення

Ці підходи менш ефективні, але вони забезпечать правильну поведінку. Вони будуть набагато менше виступ, ніж copy і update або нове розпакування, тому що вони повторюються по кожній парі ключових значень на більш високому рівні абстракції, але вони робити поважати порядок пріоритету (останні дікти мають перевагу)

Ви також можете вручну закріпити думки в усвідомленні диктування:

{k: v for d in dicts for k, v in d.items()} # iteritems in Python 2.7

або в python 2.6 (і можливо, ще в 2.4, коли були введені генераторні вирази):

dict((k, v) for d in dicts for k, v in d.items())

itertools.chain буде ланцюжувати ітератори по парі ключових значень у правильному порядку:

import itertools
z = dict(itertools.chain(x.iteritems(), y.iteritems()))

Аналіз продуктивності

Я лише збираюся провести аналіз продуктивності звичаїв, які, як відомо, поводяться правильно.

import timeit

Наступне зроблено в Ubuntu 14.04

У Python 2.7 (система Python):

>>> min(timeit.repeat(lambda: merge_two_dicts(x, y)))
0.5726828575134277
>>> min(timeit.repeat(lambda: {k: v for d in (x, y) for k, v in d.items()} ))
1.163769006729126
>>> min(timeit.repeat(lambda: dict(itertools.chain(x.iteritems(), y.iteritems()))))
1.1614501476287842
>>> min(timeit.repeat(lambda: dict((k, v) for d in (x, y) for k, v in d.items())))
2.2345519065856934

У Python 3.5 (deadsnakes PPA):

>>> min(timeit.repeat(lambda: {**x, **y}))
0.4094954460160807
>>> min(timeit.repeat(lambda: merge_two_dicts(x, y)))
0.7881555100320838
>>> min(timeit.repeat(lambda: {k: v for d in (x, y) for k, v in d.items()} ))
1.4525277839857154
>>> min(timeit.repeat(lambda: dict(itertools.chain(x.items(), y.items()))))
2.3143140770262107
>>> min(timeit.repeat(lambda: dict((k, v) for d in (x, y) for k, v in d.items())))
3.2069112799945287

Ресурси на словниках


3352
2017-11-10 22:11





У вашому випадку, що ви можете зробити, це:

z = dict(x.items() + y.items())

Це буде, як ви хочете це, поставити останній дикт в z, і внесіть значення для клавіші b бути належним чином переоцінено другим (y) значення диктату:

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> z = dict(x.items() + y.items())
>>> z
{'a': 1, 'c': 11, 'b': 10}

Якщо ви використовуєте Python 3, це лише трохи складніше. Створювати z:

>>> z = dict(list(x.items()) + list(y.items()))
>>> z
{'a': 1, 'c': 11, 'b': 10}

1440
2017-09-02 07:50





Альтернатива:

z = x.copy()
z.update(y)

547
2017-09-02 13:00



Щоб з'ясувати, чому це не відповідає критерію, заданому питанням: це не одне вираз, і він не повертає z. - Alex
@Alex іноді оригінальне питання не є правильним питанням. Якщо вам потрібно використовувати непітонський монстр регулярного виразу, наприклад, зробити щось у одному рядку або два елегантні рядки. зробити X, потім використовувати дві лінії. The {**x, **y} Підхід у прийнятій відповіді - це чудовисько. - neuronet
@neuronet кожен онлінер звичайно просто переміщує код, який має відбутися в іншому компоненті і вирішує його там. це, безумовно, одне з випадків. але для інших мов є більш приємні конструкції, ніж python. і, маючи референдуально прозорий варіант, який повертає його елемент, це добре мати річ. - Alex
Покладіть це таким чином: якщо вам потрібно додати два рядки коментарів, пояснюючи ваш рядок коду людям, котрі ви передаєте свій код, щоб ... ви дійсно зробили це в одному рядку? :) Я повністю погоджуюсь з тим, що Python для цього не годиться: має бути набагато простіший спосіб. Хоча ця відповідь є більш пітонічною, чи дійсно все це явне чи зрозуміле? Update це не одне з "основних" функцій, які люди прагнуть багато чого використовувати. - neuronet


Інший, більш стислий варіант:

z = dict(x, **y)

Примітка: це стало популярною відповіддю, але важливо зазначити, що якщо y має будь-які ключі, що не є рядковими, той факт, що це взагалі працює, є зловживанням деталей реалізації CPython, і це не працює в Python 3, або в PyPy, IronPython або Jython. Крім того, Гвідо не є фанатом. Тому я не можу рекомендувати цю техніку для портативного коду, сумісного з перехресно-впровадження, що дійсно означає, що його слід повністю уникати.


273
2017-09-02 15:52





Це, мабуть, не буде популярною відповіддю, але ви майже напевно не бажаєте це робити. Якщо вам потрібна копія, яка є об'єднанням, скористайтеся копією (або глибока копія, залежно від того, що ви хочете), а потім оновити. Дві лінії коду набагато легше читаються (більше Pythonic), ніж створення єдиного рядка з .items () + .items (). Явний є кращим, ніж неявний.

Крім того, при використанні .items () (попередньо Python 3.0) ви створюєте новий список, що містить елементи з dict. Якщо ваші словники великі, то це досить багато накладних витрат (два великих списка, які будуть викинуті, як тільки створюється об'єднаний дукт). update () може працювати більш ефективно, тому що він може проходити через другий елемент dict по одному.

З точки зору час:

>>> timeit.Timer("dict(x, **y)", "x = dict(zip(range(1000), range(1000)))\ny=dict(zip(range(1000,2000), range(1000,2000)))").timeit(100000)
15.52571702003479
>>> timeit.Timer("temp = x.copy()\ntemp.update(y)", "x = dict(zip(range(1000), range(1000)))\ny=dict(zip(range(1000,2000), range(1000,2000)))").timeit(100000)
15.694622993469238
>>> timeit.Timer("dict(x.items() + y.items())", "x = dict(zip(range(1000), range(1000)))\ny=dict(zip(range(1000,2000), range(1000,2000)))").timeit(100000)
41.484580039978027

ІМО - мінімальне уповільнення між першими двома - це коштує для читабельності. Крім того, аргументи ключових слів для створення словника додані лише в Python 2.3, тоді як copy () і update () працюватимуть у старіших версіях.


168
2017-09-08 11:16





У наступному відповіді ви запитали про відносну ефективність цих двох альтернатив:

z1 = dict(x.items() + y.items())
z2 = dict(x, **y)

На моєму комп'ютері, принаймні (досить простий x86_64 запущений Python 2.5.2), альтернатива z2 це не тільки коротше і простіше, але також значно швидше. Ви можете перевірити це за допомогою timeit модуль, що поставляється з Python.

Приклад 1: ідентичні словники, що відслідковують самі собі 20 послідовних чисел:

% python -m timeit -s 'x=y=dict((i,i) for i in range(20))' 'z1=dict(x.items() + y.items())'
100000 loops, best of 3: 5.67 usec per loop
% python -m timeit -s 'x=y=dict((i,i) for i in range(20))' 'z2=dict(x, **y)' 
100000 loops, best of 3: 1.53 usec per loop

z2 виграє в 3,5 рази або близько того. Різні словники, здається, дають зовсім різні результати, але z2 завжди здається випереджати. (Якщо ви отримаєте непослідовні результати для той же випробувань, спробуйте пройти -r з числом більше, ніж за замовчуванням 3.)

Приклад 2: словники, що не перекриваються, відображення 252 коротких рядків до цілих чисел і навпаки:

% python -m timeit -s 'from htmlentitydefs import codepoint2name as x, name2codepoint as y' 'z1=dict(x.items() + y.items())'
1000 loops, best of 3: 260 usec per loop
% python -m timeit -s 'from htmlentitydefs import codepoint2name as x, name2codepoint as y' 'z2=dict(x, **y)'               
10000 loops, best of 3: 26.9 usec per loop

z2 виграє приблизно в 10 разів. Це дуже велика перемога в моїй книзі!

Після порівняння цих двох, я думав, якщо z1Невисока продуктивність роботи може пояснюватися накладними витратами на побудову двох списків предметів, що, у свою чергу, спонукало мене замислитися, чи може ця варіація працювати краще:

from itertools import chain
z3 = dict(chain(x.iteritems(), y.iteritems()))

Кілька швидких тестів, наприклад

% python -m timeit -s 'from itertools import chain; from htmlentitydefs import codepoint2name as x, name2codepoint as y' 'z3=dict(chain(x.iteritems(), y.iteritems()))'
10000 loops, best of 3: 66 usec per loop

приведи мене до висновку, що z3 дещо швидше, ніж z1, але не так швидко, як z2. Напевно, не варто все додаткового набору тексту.

У цьому обговоренні все ще відсутнє щось важливе, це порівняльне порівняння цих альтернатив з "очевидним" способом об'єднання двох списків: використання update метод Щоб спробувати зберігати речі на рівних умовах з виразами, жоден з яких не змінює x або y, я збираю зробити копію х, а не змінювати її на місці, як показано нижче:

z0 = dict(x)
z0.update(y)

Типовий результат:

% python -m timeit -s 'from htmlentitydefs import codepoint2name as x, name2codepoint as y' 'z0=dict(x); z0.update(y)'
10000 loops, best of 3: 26.9 usec per loop

Іншими словами, z0 і z2 мабуть, мають по суті однакові характеристики. Ви думаєте, що це може бути збіг? Я не....

Насправді, я хотів би стверджувати, що для чистого коду Python це неможливо зробити краще, ніж це. І якщо ви можете зробити значно краще в модулі розширення C, я думаю, що люди Python можуть бути зацікавлені в включенні вашого коду (або варіанта вашого підходу) в ядро ​​Python. Python використовує dict у багатьох місцях; оптимізація його операцій - це велика угода.

Ви також можете написати це як

z0 = x.copy()
z0.update(y)

як це робить Тоні, але (не дивно), різниця в нотації виявляється не мати ніякого вимірюваного ефекту на продуктивність. Використовуйте те, що виглядає правильно для вас. Звичайно, він абсолютно правильно зазначив, що дві версії заявки набагато легше зрозуміти.


116
2017-10-23 02:38



Це не працює в Python 3; items() не є конденсатним, і iteritems не існує. - Antti Haapala


Я хотів щось подібне, але з можливістю вказати, як змінювалися значення дублікатів ключів, тому я зламав це (але не протестував його важко). Очевидно, що це не одне вираження, але це виклик однієї функції.

def merge(d1, d2, merge_fn=lambda x,y:y):
    """
    Merges two dictionaries, non-destructively, combining 
    values on duplicate keys as defined by the optional merge
    function.  The default behavior replaces the values in d1
    with corresponding values in d2.  (There is no other generally
    applicable merge strategy, but often you'll have homogeneous 
    types in your dicts, so specifying a merge technique can be 
    valuable.)

    Examples:

    >>> d1
    {'a': 1, 'c': 3, 'b': 2}
    >>> merge(d1, d1)
    {'a': 1, 'c': 3, 'b': 2}
    >>> merge(d1, d1, lambda x,y: x+y)
    {'a': 2, 'c': 6, 'b': 4}

    """
    result = dict(d1)
    for k,v in d2.iteritems():
        if k in result:
            result[k] = merge_fn(result[k], v)
        else:
            result[k] = v
    return result

86
2017-09-04 19:08





У Python 3 ви можете використовувати колекції.ChainMap який об'єднує декілька диктов або інших зіставлень для створення єдиного оновлюваного вигляду:

>>> from collections import ChainMap
>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> z = ChainMap({}, y, x)
>>> for k, v in z.items():
        print(k, '-->', v)

a --> 1
b --> 10
c --> 11

73
2018-04-28 03:15



Але при використанні ChainMap треба бути обережним, є те, що якщо у вас є дубльовані ключі, то використовуються значення з першого відображення, і коли ви телефонуєте del скажімо, ChainMap c видаляє перше відображення цього ключа. - Prerit
@Prerit Що ще ви очікуєте зробити? Це нормальний спосіб роботи з прихованими просторами імен. Подумайте, як працює $ PATH в bash. Видалення виконуваного файлу на шляху не перешкоджає іншому виконуваному файлу з таким самим ім'ям і далі вгору. - Raymond Hettinger
@Raymond Hettinger Я згоден, просто додав обережність. Більшість людей може не знати про це. : D - Prerit
Я прийшов сюди, щоб рекомендувати простоту collections.ChainMap. - hughdbrown
Насправді, вид виходу, який ви отримали тут, може бути досягнуто навіть без використання ChainMap правильно? Я маю на увазі, все, що вам потрібно, це просто оновити z = {**x, **y} і слідувати за традиційними кроками, ось і все! - Steffi Keran Rani J


Рекурсивно / глибоко оновлювати дикт

def deepupdate(original, update):
    """
    Recursively update a dict.
    Subdict's won't be overwritten but also updated.
    """
    for key, value in original.iteritems(): 
        if key not in update:
            update[key] = value
        elif isinstance(value, dict):
            deepupdate(value, update[key]) 
    return update

Демонстрація:

pluto_original = {
    'name': 'Pluto',
    'details': {
        'tail': True,
        'color': 'orange'
    }
}

pluto_update = {
    'name': 'Pluutoo',
    'details': {
        'color': 'blue'
    }
}

print deepupdate(pluto_original, pluto_update)

Виходи:

{
    'name': 'Pluutoo',
    'details': {
        'color': 'blue',
        'tail': True
    }
}

Спасибі за червонувку для редагування.


61
2017-11-29 11:52





Найкраща версія, яку я міг би подумати, не використовуючи копію:

from itertools import chain
x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
dict(chain(x.iteritems(), y.iteritems()))

Це швидше, ніж dict(x.items() + y.items()) але не так швидко, як n = copy(a); n.update(b), принаймні на CPython. Ця версія також працює в Python 3, якщо ви зміните iteritems() до items(), який автоматично виконується інструментом 2to3.

Особисто я люблю цю версію краще, тому що вона досить добре описує те, що я хочу в одному функціональному синтаксисі. Єдина незначна проблема полягає в тому, що не зовсім очевидно, що значення з y мають пріоритет над значеннями з x, але я не вірю, що це важко зрозуміти.


54
2017-10-14 18:55