Як злити два словники в одному виразі в Python?


4782

У мене є два словники Python, і я хочу написати єдиний вираз, який повертає ці два словники, об'єднані. 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}

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

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


Що стосується випадкових випадків, коли ви використовуєте альфа Python 3.9, просто використовуйтеz = x | y
The Daleks

Відповіді:


5689

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

Для словників xі y, zперетворюється на дрібно злитий словник зі значеннями yзаміни цих на x.

  • У Python 3.5 або новішої версії:

    z = {**x, **y}
  • У 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)
  • У Python 3.9.0a4 або вище (остаточна дата випуску приблизно у жовтні 2020 р.): PEP-584 , обговорюваний тут , було втілено для подальшого спрощення цього:

    z = x | y          # NOTE: 3.9+ ONLY

Пояснення

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

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}

І це справді єдиний вираз.

Зауважте, що ми можемо також об'єднатися з буквальними позначеннями:

z = {**x, 'foo': 1, 'bar': 2, **y}

і зараз:

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

Зараз він показує, як реалізовано в графіку випуску для 3.5, PEP 478 , і тепер пробився в документ « Що нового в Python 3.5» .

Однак, оскільки багато організацій продовжують працювати на Python 2, можливо, ви хочете зробити це сумісно назад. Класично пітонічний спосіб, доступний в 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, але хочеться одного виразу

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

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матиме пріоритет над dicts aдо f, і так далі.

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

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

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

У Python 2 ви створюєте два списки в пам'яті для кожного диктату, створюєте третій список у пам'яті довжиною, що дорівнює довжині перших двох разом, а потім відкидаєте всі три списки для створення диктату. У 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конструктор, і він дуже швидкий і ефективний для пам'яті (навіть трохи більше, ніж наш двоетапний процес), але якщо ви точно не знаєте, що відбувається тут (тобто другий дікт передається як аргументи ключових слів в дікт конструктор), це важко читати, це не призначене використання, а значить, не піфонічне.

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

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

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

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

Мені добре, що оголосив наказ ({}, ** {1: 3}) незаконним, оскільки врешті-решт це зловживання механізмом **.

і

Мабуть, dict (x, ** y) відбувається навколо "cool hack" для "call x.update (y) та return x". Особисто я вважаю це більш зневажливим, ніж крутим.

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

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

замість

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

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

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

Знову ж, це не працює для 3, коли клавіші не є рядками. Договір неявного виклику полягає в тому, що простори імен приймають звичайні дикти, тоді як користувачі повинні передавати лише аргументи ключових слів, які є рядками. Усі інші дзвінки переслідували це. 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 все більше застаріває.

{**x, **y}схоже, не обробляє вкладені словники. вміст вкладених ключів просто перезаписується, а не зливається [...] Мене в результаті спалили ці відповіді, які не зливаються рекурсивно, і я був здивований, що ніхто про це не згадував. У моєму тлумаченні слова "злиття" ці відповіді описують "оновлення одного диктату з іншим", а не злиття.

Так. Я мушу повернути вас до питання, яке вимагає неглибокого злиття двох словників, причому значення першого будуть переписані другими - в одному виразі.

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

from copy import deepcopy

def dict_of_dicts_merge(x, y):
    z = {}
    overlapping_keys = x.keys() & y.keys()
    for key in overlapping_keys:
        z[key] = dict_of_dicts_merge(x[key], y[key])
    for key in x.keys() - overlapping_keys:
        z[key] = deepcopy(x[key])
    for key in y.keys() - overlapping_keys:
        z[key] = deepcopy(y[key])
    return z

Використання:

>>> x = {'a':{1:{}}, 'b': {2:{}}}
>>> y = {'b':{10:{}}, 'c': {11:{}}}
>>> dict_of_dicts_merge(x, y)
{'b': {2: {}, 10: {}}, 'a': {1: {}}, 'c': {11: {}}}

Створення непередбачуваних випадків для інших типів значень далеко виходить за рамки цього питання, тому я накажу вам свою відповідь на канонічне запитання на тему "Злиття словників" .

Менш ефективні, але правильні спеціальні

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

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

{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 (глухонімих 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

Ресурси зі словників


9
@MohammadAzim "лише рядки" застосовуються лише до розширення аргументів ключових слів у викликах, а не до узагальненого синтаксису розпакування. Щоб продемонструвати, що це працює: {**{(0, 1):2}}->{(0, 1): 2}
Аарон Холл

36
короткі відповіді на кшталт z = {**x, **y}насправді мене стимулюють
pcko1

1
Це може бути змінено, коли прийнято PEP-0584. Новий оператор об'єднання буде реалізований із таким синтаксисом:x | y
Callam Delaney

2
Коли відповідь потребує резюме вгорі, він занадто довгий.
Gringo Suave

2
Привіт, верх - це підсумок, так. До вас. Все це було б чудовою публікацією в блозі. Примітка Py 3.4 і нижче - EOL, 3.5 наближається до EOL у 2020-09.
Gringo Suave

1616

У вашому випадку ви можете:

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}

Якщо ви використовуєте Python версії 3.9.0a4 або новішої, ви можете безпосередньо використовувати:

x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
z = x | y
print(z)

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

1
Не використовуйте це, оскільки це дуже неефективно. (Дивіться результати часу, наведені нижче.) Можливо, це було б потрібно в дні Py2, якщо функція обгортки не була можливою, але ці дні минули.
Gringo Suave

633

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

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

83
Щоб уточнити, чому це не відповідає критерию, поставленому питанням: це не один вираз і він не повертає z.
Алекс

2
@neuronet кожен oneliner, як правило, просто переміщує код, який повинен перейти в інший компонент і вирішує його там. це, безумовно, один із випадків. але інші мови мають для цього кращі конструкції, ніж python. і мати референтно прозорий варіант, який повертає його елемент, приємно мати річ.
Олексій

12
Викладіть так: якщо вам потрібно помістити два рядки коментарів, що пояснюють ваш один рядок коду людям, яким ви передаєте свій код ... чи справді ви це зробили в один рядок? :) Я повністю погоджуюся, що Python для цього не годиться: має бути набагато простіший спосіб. Хоча ця відповідь є більш пітонічною, чи це насправді все явне чи зрозуміле? Updateне є однією з "основних" функцій, якими люди, як правило, користуються багато.
eric

Що ж, якщо люди наполягають на тому, щоб зробити його лінійкою, ви завжди можете це зробити (lambda z: z.update(y) or z)(x.copy()): P
буксир

340

Ще один, більш стислий варіант:

z = dict(x, **y)

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


Добре працює в Python 3 і PyPy і PyPy 3 , не може говорити з Jython або Iron. З огляду на те, що ця закономірність є чітко задокументованою (див. Третю форму конструктора в цій документації), я б заперечував, що це не "деталізація реалізації", а навмисне використання функцій.
amcgregor

5
@amcgregor Ви пропустили ключову фразу "якщо у вас є будь-які рядкові клавіші". Ось що не працює в Python3; той факт, що він працює в CPython 2, є деталлю реалізації, на яку не можна покластися. Якщо всі ваші ключі гарантовано є рядками, це повністю підтримується варіант.
Карл Мейер

214

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

Крім того, використовуючи .items () (перед Python 3.0), ви створюєте новий список, який містить елементи з диктату. Якщо ваші словники великі, то це досить багато накладних (два великих списки, які будуть викинуті, як тільки буде створений об'єднаний дикт). 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

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


150

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

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, я зроблю копію x замість того, щоб змінити її на місці, як слід:

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)

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


5
Це не працює в Python 3; items()не підлягає застосуванню і iteritemsне існує.
Антті Хаапала

127

У Python 3.0 та новіших версіях ви можете використовувати collections.ChainMapгрупи, які групують декілька диктів або інших відображень разом, щоб створити єдиний оновлений вид:

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

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

Оновлення для Python 3.5 та новіших версій : Ви можете використовувати розширення словника PEP 448 та розпакування. Це швидко і просто:

>>> x = {'a':1, 'b': 2}
>>> y = y = {'b':10, 'c': 11}
>>> {**x, **y}
{'a': 1, 'b': 10, 'c': 11}

3
Але слід бути обережним при використанні ChainMap, є заперечення, що якщо у вас є дублюючі ключі, значення, отримані під час першого відображення, звикають, а коли ви дзвоните, delскажімо, ChainMap c видалить перше відображення цього ключа.
Вбивця

7
@Prerit Що б ви ще очікували від цього? Це нормальний спосіб роботи ланцюгових просторів імен. Розглянемо, як $ PATH працює в баші. Видалення виконуваного файлу на шляху не виключає іншого виконуваного файлу з таким самим іменем далі за течією.
Реймонд Хеттінгер

2
@Raymond Hettinger Я згоден, просто додав обережності. Більшість людей може не знати про це. : D
вбивця

@Prerit Ви можете кинути, щоб dictуникнути цього, тобто:dict(ChainMap({}, y, x))
wjandrea

113

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

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

88

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

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

Дякуємо, що редагували за зміни.


1
Це не дає відповіді на запитання. Питання чітко запитує новий словник, z, з оригінальних словників, x і y, зі значеннями y, які замінюють значення x, - не оновлений словник. Ця відповідь змінює y на місці, додаючи значення з x. Гірше, що вони не копіюють ці значення, тому можна додатково змінити модифікований словник, y, а модифікації можуть бути відображені в словнику x. @ Jérôme Я сподіваюся, що цей код не спричиняє помилок у вашій програмі - принаймні, подумайте про використання глибокої копії для копіювання значень.
Аарон Холл

1
@AaronHall погодився, що це не відповідає на питання. Але це відповідає моїй потребі. Я розумію ці обмеження, але це не проблема в моєму випадку. Думаючи про це, можливо, назва вводить в оману, оскільки це може викликати глибоку копію, якої вона не надає. Але це стосується глибокого гніздування. Ось ще одна реалізація від Martellibot: stackoverflow.com/questions/3232943/… .
Jérôme

72

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

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, але я не вірю, що це важко зрозуміти.


71

Python 3.5 (PEP 448) дозволяє приємніший варіант синтаксису:

x = {'a': 1, 'b': 1}
y = {'a': 2, 'c': 2}
final = {**x, **y} 
final
# {'a': 2, 'b': 1, 'c': 2}

Або навіть

final = {'a': 1, 'b': 1, **x, **y}

У Python 3.9 ви також використовуєте | та | = з наведеним нижче прикладом з PEP 584

d = {'spam': 1, 'eggs': 2, 'cheese': 3}
e = {'cheese': 'cheddar', 'aardvark': 'Ethel'}
d | e
# {'spam': 1, 'eggs': 2, 'cheese': 'cheddar', 'aardvark': 'Ethel'}

У чому спосіб це рішення краще, ніж розчинення dict(x, **y)? Як ви (@CarlMeyer) згадували в примітці власної відповіді ( stackoverflow.com/a/39858/2798610 ), Гвідо вважає це рішення незаконним .
Blackeagle52

14
Гвідо не любить dict(x, **y)з (дуже доброї) причини, що він покладається yлише на те, щоб мати лише ключі, які мають дійсні імена аргументів ключових слів (якщо ви не використовуєте CPython 2.7, де обманює конструктор dict). Це заперечення / обмеження не поширюється на PEP 448, який узагальнює **синтаксис , що розпаковується, для диктування літералів. Тож це рішення має таку ж чіткість, як dict(x, **y)і без зворотного боку.
Карл Мейєр

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

Для елементів, що мають ключі в обох словниках ('b'), ви можете керувати, який з них закінчується у висновку, поклавши останній.


У python 3 ви отримаєте TypeError: непідтримувані типи операндів для +: 'dict_items' та 'dict_items' ... вам слід інкапсулювати кожен dict списком () на зразок: dict (список (x.items ()) + список (y.items ()))
justSaid

49

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

x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
z4 = {}
z4.update(x)
z4.update(y)

Це так само швидко, як z0 і злий z2, згаданий вище, але легко зрозуміти і змінити.


3
але це три твердження, а не один вираз
fortran

14
Так! Згадані рішення одновираження або повільні, або злі. Хороший код читабельний та ремонтопридатний. Тож проблема - це питання, а не відповідь. Ми повинні просити найкраще вирішення проблеми, а не однолінійне рішення.
фобій

7
Втратити z4 = {}і змінити наступний рядок на z4 = x.copy()- краще, ніж просто хороший код не робить зайвих речей (що робить його ще більш читабельним та ремонтопридатним).
мартіно

3
Ваша пропозиція змінила б це на відповідь Метьюса. Незважаючи на те, що його відповідь чудова, я вважаю, що міна є легшою для читання і її краще доглядати. Додатковий рядок буде поганим, лише якщо це коштуватиме час виконання.
фобій

47
def dict_merge(a, b):
  c = a.copy()
  c.update(b)
  return c

new = dict_merge(old, extras)

Серед таких тінистих і сумнівних відповідей цей яскравий приклад є єдиним і єдиним хорошим способом злиття диктів на Python, схвалених самим диктатором Жидо ван Россумом ! Хтось ще запропонував половину цього, але не поставив це у функцію.

print dict_merge(
      {'color':'red', 'model':'Mini'},
      {'model':'Ferrari', 'owner':'Carl'})

дає:

{'color': 'red', 'owner': 'Carl', 'model': 'Ferrari'}

39

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

x = {'a':1, 'b':2}
y = {'b':10, 'c':11}
z = (lambda a, b: (lambda a_copy: a_copy.update(b) or a_copy)(a.copy()))(x, y)
print z
{'a': 1, 'c': 11, 'b': 10}
print x
{'a': 1, 'b': 2}

Як було запропоновано вище, використання двох рядків або написання функції, мабуть, є кращим способом.



30

У python3 itemsметод більше не повертає список , а швидше перегляд , який діє як набір. У цьому випадку вам потрібно буде взяти встановлений союз, оскільки з'єднання з +не буде працювати:

dict(x.items() | y.items())

Для поведінки, подібної python3, у версії 2.7, viewitemsметод повинен працювати замість items:

dict(x.viewitems() | y.viewitems())

Я все одно віддаю перевагу цьому позначенню, оскільки здається більш природним сприймати це як задану операцію об'єднання, а не конкатенацію (як показує заголовок).

Редагувати:

Ще кілька балів для python 3. По-перше, зауважте, що dict(x, **y)трюк не працюватиме в python 3, якщо ключі не yє рядками.

Крім того, відповідь Chainmap Реймонда Хеттінгера є досить елегантною, оскільки вона може брати довільну кількість диктовок як аргументів, але з документів це виглядає так, що вона послідовно переглядає список усіх диктів для кожного пошуку:

Пошук шукає послідовні відображення послідовно, поки ключ не знайдеться.

Це може сповільнити вас, якщо у вашій програмі багато пошукових запитів:

In [1]: from collections import ChainMap
In [2]: from string import ascii_uppercase as up, ascii_lowercase as lo; x = dict(zip(lo, up)); y = dict(zip(up, lo))
In [3]: chainmap_dict = ChainMap(y, x)
In [4]: union_dict = dict(x.items() | y.items())
In [5]: timeit for k in union_dict: union_dict[k]
100000 loops, best of 3: 2.15 µs per loop
In [6]: timeit for k in chainmap_dict: chainmap_dict[k]
10000 loops, best of 3: 27.1 µs per loop

Так про порядок повільніше для пошуку. Я шанувальник Chainmap, але виглядає менш практичним там, де може бути багато пошукових запитів.


22

Зловживання, що призводить до одновиражного рішення для відповіді Метью :

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> z = (lambda f=x.copy(): (f.update(y), f)[1])()
>>> z
{'a': 1, 'c': 11, 'b': 10}

Ви сказали, що хочете одного виразу, тож я зловживав lambdaприв’язувати ім'я, а кортежі, щоб перекрити межу одного вираження лямбда Не соромлячись.

Ви також можете це зробити, звичайно, якщо ви не переймаєтесь їх копіюванням:

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

22

Просте рішення з використанням itertools, що зберігає порядок (останні дикти мають перевагу)

import itertools as it
merge = lambda *args: dict(it.chain.from_iterable(it.imap(dict.iteritems, args)))

І це використання:

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> merge(x, y)
{'a': 1, 'b': 10, 'c': 11}

>>> z = {'c': 3, 'd': 4}
>>> merge(x, y, z)
{'a': 1, 'b': 10, 'c': 3, 'd': 4}


16

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

Наступні приклади:

a = { 'one': { 'depth_2': True }, 'two': True }
b = { 'one': { 'extra': False } }
print dict(a.items() + b.items())

Можна очікувати результату приблизно такого:

{ 'one': { 'extra': False', 'depth_2': True }, 'two': True }

Натомість ми отримуємо це:

{'two': True, 'one': {'extra': False}}

Запис "one" повинен був містити "deep_2" та "extra" як елементи всередині свого словника, якби це справді було злиттям.

Використання ланцюга також не працює:

from itertools import chain
print dict(chain(a.iteritems(), b.iteritems()))

Призводить до:

{'two': True, 'one': {'extra': False}}

Глибоке злиття, яке дав rcwesick, також створює той же результат.

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


11

(Тільки для Python2.7 *; є більш прості рішення для Python3 *.)

Якщо ви не проти імпортувати стандартний модуль бібліотеки, ви можете це зробити

from functools import reduce

def merge_dicts(*dicts):
    return reduce(lambda a, d: a.update(d) or a, dicts, {})

( or aБіт у lambdaнеобхідному, тому що dict.updateзавжди повертається Noneдо успіху.)


11

Якщо ви не проти мутувати x,

x.update(y) or x

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

Методи, що мутують у стандартній бібліотеці (як .update()), повертаються Noneза домовленістю, тому ця модель також буде працювати на них. Якщо ви використовуєте метод, який не відповідає цій умові, orможливо, це не спрацює. Але, ви можете використовувати кортеж дисплея та індексу, щоб зробити це єдиним виразом. Це працює незалежно від того, що оцінює перший елемент.

(x.update(y), x)[-1]

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

(lambda x: x.update(y) or x)({'a': 1, 'b': 2})

Хоча це не так відрізняється від наступного використання нового оператора моржів (лише Python 3.8+):

(x := {'a': 1, 'b': 2}).update(y) or x

Якщо ви хочете отримати копію, стиль PEP 448 найпростіший {**x, **y}. Але якщо це недоступно у вашій (старшій) версії Python, нехай шаблон працює і тут.

(lambda z: z.update(y) or z)(x.copy())

(Це, звичайно, еквівалентно (z := x.copy()).update(y) or z, але якщо ваша версія Python для цього достатньо нова, тоді буде доступний стиль PEP 448.)


10

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

def merge(*dicts, **kv): 
      return { k:v for d in list(dicts) + [kv] for k,v in d.items() }

Використання (тестоване в пітоні 3):

assert (merge({1:11,'a':'aaa'},{1:99, 'b':'bbb'},foo='bar')==\
    {1: 99, 'foo': 'bar', 'b': 'bbb', 'a': 'aaa'})

assert (merge(foo='bar')=={'foo': 'bar'})

assert (merge({1:11},{1:99},foo='bar',baz='quux')==\
    {1: 99, 'foo': 'bar', 'baz':'quux'})

assert (merge({1:11},{1:99})=={1: 99})

Ви можете замість цього використати лямбда.


10

Проблема, яку я маю з переліченими на сьогодні рішеннями, полягає в тому, що в об'єднаному словнику значення для ключа "b" дорівнює 10, але, на мій погляд, воно повинно бути 12. У цьому світлі я представляю наступне:

import timeit

n=100000
su = """
x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
"""

def timeMerge(f,su,niter):
    print "{:4f} sec for: {:30s}".format(timeit.Timer(f,setup=su).timeit(n),f)

timeMerge("dict(x, **y)",su,n)
timeMerge("x.update(y)",su,n)
timeMerge("dict(x.items() + y.items())",su,n)
timeMerge("for k in y.keys(): x[k] = k in x and x[k]+y[k] or y[k] ",su,n)

#confirm for loop adds b entries together
x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
for k in y.keys(): x[k] = k in x and x[k]+y[k] or y[k]
print "confirm b elements are added:",x

Результати:

0.049465 sec for: dict(x, **y)
0.033729 sec for: x.update(y)                   
0.150380 sec for: dict(x.items() + y.items())   
0.083120 sec for: for k in y.keys(): x[k] = k in x and x[k]+y[k] or y[k]

confirm b elements are added: {'a': 1, 'c': 11, 'b': 12}

1
Ви можете бути зацікавлені в cytoolz.merge_with( toolz.readthedocs.io/en/latest / ... )
BLI

10

Це так нерозумно, що .updateнічого не повертає.
Я просто використовую просту функцію помічника для вирішення проблеми:

def merge(dict1,*dicts):
    for dict2 in dicts:
        dict1.update(dict2)
    return dict1

Приклади:

merge(dict1,dict2)
merge(dict1,dict2,dict3)
merge(dict1,dict2,dict3,dict4)
merge({},dict1,dict2)  # this one returns a new copy

10
from collections import Counter
dict1 = {'a':1, 'b': 2}
dict2 = {'b':10, 'c': 11}
result = dict(Counter(dict1) + Counter(dict2))

Це повинно вирішити вашу проблему.


9

Це можна зробити за допомогою розуміння одного диктату:

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> { key: y[key] if key in y else x[key]
      for key in set(x) + set(y)
    }

На мій погляд, найкраща відповідь на частину "одного виразу", оскільки додаткові функції не потрібні, і вона коротка.


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

2
все залежить від версії пітона, яку ми використовуємо. В 3.5 і вище {** x, ** y} дає словник, що
поєднується

9

Після випуску програми Python 3.8 ( запланованого на 20 жовтня 2019 року ) з'явиться нова опція , завдяки PEP 572: Assignment Expressions . Новий оператор виразу присвоєння :=дозволяє призначити результат copyі все ще використовувати його для виклику update, залишаючи комбінованому коду єдиний вираз, а не два твердження, змінюючи:

newdict = dict1.copy()
newdict.update(dict2)

до:

(newdict := dict1.copy()).update(dict2)

при цьому поводитись однаково в усіх відношеннях. Якщо ви також повинні повернути отриманий результат dict(ви попросили вираз, що повертає dict; вищезазначене створює та призначає newdict, але не повертає його, тому ви не можете використовувати його для передачі аргументу функції, як, а-ля myfunc((newdict := dict1.copy()).update(dict2))) , тоді просто додайте or newdictдо кінця (оскільки updateповертається None, що є помилковим, він буде потім оцінювати та повертати newdictяк результат виразу):

(newdict := dict1.copy()).update(dict2) or newdict

Важливий застереження: Загалом, я б заважав такому підходу на користь:

newdict = {**dict1, **dict2}

Підхід до розпакування є більш чітким (тому, хто знає про узагальнене розпакування, в першу чергу, що вам слід ), взагалі не потрібне ім'я для результату (тому набагато більш стислим при побудові тимчасового, який негайно передається в функція або включена в list/ tupleбуквальний або подібний), і майже напевно швидше, будучи (на CPython) приблизно еквівалентною:

newdict = {}
newdict.update(dict1)
newdict.update(dict2)

але виконується на шарі С, використовуючи конкретний dictAPI, тому жоден динамічний пошук способу пошуку / прив'язки або надіслання набір функцій виклику функцій (де (newdict := dict1.copy()).update(dict2)неминуче ідентичний оригінальному дволаймеру за поведінкою, виконуючи роботу в дискретних кроках, при динамічному пошуку / прив'язка / виклик методів.

Він також більш розширюваний, оскільки об'єднання трьох dicts очевидно:

 newdict = {**dict1, **dict2, **dict3}

де використання виразів призначення не масштабується так; найближчим ви могли б стати:

 (newdict := dict1.copy()).update(dict2), newdict.update(dict3)

або без тимчасового Noneнабору s, але з перевірки правдивості кожного Noneрезультату:

 (newdict := dict1.copy()).update(dict2) or newdict.update(dict3)

кожний з яких, очевидно , набагато потворніше, і включає в себе додаткові неефективних (або даремно тимчасовий tupleз Noneз коми для розділення, або безглуздих тестувань truthiness кожного update«и Noneповернення для orподілу).

Єдина реальна перевага підходу до виразу присвоєння виникає, якщо:

  1. У вас є загальний код, який потребує обробки і sets і dicts (обидва вони підтримують copyі update, тому код працює приблизно так, як ви цього очікували)
  2. Ви очікуєте, що ви отримаєте довільні об'єкти , схожі на диктант , не лише dictсамі, і повинні зберігати тип та семантику лівої сторони (а не закінчувати простою dict). Хоча це myspecialdict({**speciala, **specialb})може спрацювати, це матиме додатковий тимчасовий характер dict, а якщо myspecialdictє функції, dictзвичайні dicts не можуть зберегти (наприклад, звичайний s тепер зберігає порядок на основі першої появи ключа та значення на основі останньої появи ключа; можливо, ви захочете той, що зберігає порядок на основі останньогопоява ключа, тому оновлення значення також переміщує його до кінця), то семантика була б неправильною. Оскільки версія виразу присвоєння використовує названі методи (які, мабуть, перевантажені, щоб поводитись належним чином), він ніколи не створює а dict(взагалі, якщо dict1це вже не було dict), зберігаючи початковий тип (і семантику оригінального типу), уникаючи тимчасових тем.

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

Цей метод перезаписується xйого копією. Якщо xце аргумент функції, це не спрацює (див. Приклад )
bartolo-otrit
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.