Как объединить два словаря в одном выражении? - PullRequest
3974 голосов
/ 02 сентября 2008

У меня есть два словаря 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() - это то, что я тоже ищу.)

Ответы [ 40 ]

4 голосов
/ 18 ноября 2016

Для этого вы можете использовать toolz.merge([x, y]).

4 голосов
/ 18 мая 2016

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

В зависимости от варианта использования может не потребоваться создание «реального» объединенного словаря из заданных входных словарей. A view , который делает это, может быть достаточным во многих случаях, т.е. е. объект, который действует как объединенный словарь, не вычисляя его полностью. Ленивая версия объединенного словаря, так сказать.

В Python это довольно просто и может быть сделано с помощью кода, показанного в конце моего поста. Учитывая это, ответ на оригинальный вопрос будет:

z = MergeDict(x, y)

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

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

Если вы когда-либо чувствовали, что предпочли бы иметь реальное объединение dict, то вызов dict(z) даст его (но, конечно, намного дороже, чем другие решения, поэтому об этом просто стоит упомянуть).

Вы также можете использовать этот класс для создания своего рода словаря для копирования при записи:

a = { 'x': 3, 'y': 4 }
b = MergeDict(a)  # we merge just one dict
b['x'] = 5
print b  # will print {'x': 5, 'y': 4}
print a  # will print {'y': 4, 'x': 3}

Вот простой код MergeDict:

class MergeDict(object):
  def __init__(self, *originals):
    self.originals = ({},) + originals[::-1]  # reversed

  def __getitem__(self, key):
    for original in self.originals:
      try:
        return original[key]
      except KeyError:
        pass
    raise KeyError(key)

  def __setitem__(self, key, value):
    self.originals[0][key] = value

  def __iter__(self):
    return iter(self.keys())

  def __repr__(self):
    return '%s(%s)' % (
      self.__class__.__name__,
      ', '.join(repr(original)
          for original in reversed(self.originals)))

  def __str__(self):
    return '{%s}' % ', '.join(
        '%r: %r' % i for i in self.iteritems())

  def iteritems(self):
    found = set()
    for original in self.originals:
      for k, v in original.iteritems():
        if k not in found:
          yield k, v
          found.add(k)

  def items(self):
    return list(self.iteritems())

  def keys(self):
    return list(k for k, _ in self.iteritems())

  def values(self):
    return list(v for _, v in self.iteritems())
3 голосов
/ 30 сентября 2014

Объединение двух словарей ОП будет примерно таким:

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

В частности, объединение двух сущностей (x и y) содержит все элементы x и / или y. К сожалению, то, о чем просит ФП, не является профсоюзом, несмотря на название поста.

Мой код ниже не является ни элегантным, ни однострочным, но я считаю, что это согласуется со значением союза.

Из примера ОП:

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

z = {}
for k, v in x.items():
    if not k in z:
        z[k] = [(v)]
    else:
        z[k].append((v))
for k, v in y.items():
    if not k in z:
        z[k] = [(v)]
    else:
        z[k].append((v))

{'a': [1], 'b': [2, 10], 'c': [11]}

Можно ли изменить списки, которые можно изменить, но вышеприведенное сработает, если словарь содержит списки (и вложенные списки) в качестве значений в любом словаре.

3 голосов
/ 28 февраля 2019

Будет новая опция, когда релизы Python 3.8 ( запланированы на 20 октября, 2019 ), благодаря PEP 572: выражения назначения . Новый оператор выражения присваивания := позволяет вам присвоить результат 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)

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

Это также более расширяемо, так как объединение трех dict s очевидно:

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

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

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

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

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

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

Единственное реальное преимущество в подходе выражения присваивания имеет место, если:

  1. У вас есть универсальный код, который должен обрабатывать как set s, так и dict s (оба они поддерживают copy и update, поэтому код работает примерно так, как вы ожидаете в)
  2. Вы ожидаете получить произвольные объекты типа dict , а не только dict, и должны сохранять тип и семантику левой стороны (вместо того, чтобы заканчиваться на равнина dict). Хотя myspecialdict({**speciala, **specialb}) может работать, это может потребовать дополнительного временного dict, и если myspecialdict имеет функции, которые обычные dict не могут сохранить (например, обычные dict s теперь сохраняют порядок, основанный на первом появлении ключа и значение, основанное на последнем появлении ключа; может потребоваться тот, который сохраняет порядок на основе last появления ключа, поэтому обновление значения также перемещает его в конец), тогда семантика будет неправильно. Поскольку версия выражения присваивания использует именованные методы (которые, по-видимому, перегружены для правильного поведения), она вообще никогда не создает dict (если dict1 уже не была dict), сохраняя исходный тип (и семантику исходного типа). ), все время избегая каких-либо временных.
1 голос
/ 11 мая 2018

Я думаю, что мои уродливые однострочники здесь просто необходимы.

z = next(z.update(y) or z for z in [x.copy()])
# or
z = (lambda z: z.update(y) or z)(x.copy())
  1. Dicts объединяются.
  2. Одиночное выражение.
  3. Никогда не смей им пользоваться.

P.S. Это решение работает в обеих версиях Python. Я знаю, что в Python 3 есть эта {**x, **y} вещь, и это правильная вещь для использования (а также переход на Python 3, если у вас все еще есть Python 2, это правильная вещь).

1 голос
/ 16 апреля 2018

Это выражение для Python 3.5 или выше, которое объединяет словари, используя reduce:

>>> from functools import reduce
>>> l = [{'a': 1}, {'b': 2}, {'a': 100, 'c': 3}]
>>> reduce(lambda x, y: {**x, **y}, l, {})
{'a': 100, 'b': 2, 'c': 3}

Примечание: это работает, даже если список словаря пуст или содержит только один элемент.

1 голос
/ 22 марта 2018

Мне было любопытно, смогу ли я побить время принятого ответа с помощью строкового подхода в одну строку:

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

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

import json
import yaml
import time
from ast import literal_eval as literal

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

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

start = time.time()
for i in range(10000):
    z = yaml.load((str(x)+str(y)).replace('}{',', '))
elapsed = (time.time()-start)
print (elapsed, z, 'stringify yaml')

start = time.time()
for i in range(10000):
    z = literal((str(x)+str(y)).replace('}{',', '))
elapsed = (time.time()-start)
print (elapsed, z, 'stringify literal')

start = time.time()
for i in range(10000):
    z = eval((str(x)+str(y)).replace('}{',', '))
elapsed = (time.time()-start)
print (elapsed, z, 'stringify eval')

start = time.time()
for i in range(10000):
    z = {k:int(v) for k,v in (dict(zip(
            ((str(x)+str(y))
            .replace('}',' ')
            .replace('{',' ')
            .replace(':',' ')
            .replace(',',' ')
            .replace("'",'')
            .strip()
            .split('  '))[::2], 
            ((str(x)+str(y))
            .replace('}',' ')
            .replace('{',' ').replace(':',' ')
            .replace(',',' ')
            .replace("'",'')
            .strip()
            .split('  '))[1::2]
             ))).items()}
elapsed = (time.time()-start)
print (elapsed, z, 'stringify replace')

start = time.time()
for i in range(10000):
    z = json.loads(str((str(x)+str(y)).replace('}{',', ').replace("'",'"')))
elapsed = (time.time()-start)
print (elapsed, z, 'stringify json')

start = time.time()
for i in range(10000):
    z = merge_two_dicts(x, y)
elapsed = (time.time()-start)
print (elapsed, z, 'accepted')

Результаты:

7.693928956985474 {'c': 11, 'b': 10, 'a': 1} stringify yaml
0.29134678840637207 {'c': 11, 'b': 10, 'a': 1} stringify literal
0.2208399772644043 {'c': 11, 'b': 10, 'a': 1} stringify eval
0.1106564998626709 {'c': 11, 'b': 10, 'a': 1} stringify replace
0.07989692687988281 {'c': 11, 'b': 10, 'a': 1} stringify json
0.005082368850708008 {'c': 11, 'b': 10, 'a': 1} accepted

Из этого я узнал, что подход json является самым быстрым (из тех, что пытались) возвратить словарь из словарной строки; гораздо быстрее (примерно 1/4 времени) того, что я считал нормальным методом, используя ast. Я также узнал, что yaml подхода следует избегать любой ценой.

Да, я понимаю, что это не самый лучший / правильный способ, поэтому, пожалуйста, не отрицайте отрицательное забвение, ноль - это хорошо. Мне было любопытно, если бы это было быстрее, а это не так; Я отправил, чтобы доказать это так.

1 голос
/ 05 декабря 2013

У меня есть решение, которое здесь не указано

z = {}
z.update(x) or z.update(y)

Это не обновит x так же, как y. Спектакль? Я не думаю, что это будет ужасно медленно: -)

ПРИМЕЧАНИЕ. Предполагается, что это операция или операция, а не операция. Отредактировано для исправления кода.

1 голос
/ 30 мая 2017

Вопрос помечен python-3x, но, учитывая, что это относительно недавнее добавление, и что наиболее одобренный принятый ответ в значительной степени касается решения Python 2.x, я осмелюсь добавить один вкладыш, который привлекает раздражающее особенность понимания списка в Python 2.x, то есть утечка имени ...

$ python2
Python 2.7.13 (default, Jan 19 2017, 14:48:08) 
[GCC 6.3.0 20170118] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> [z.update(d) for z in [{}] for d in (x, y)]
[None, None]
>>> z
{'a': 1, 'c': 11, 'b': 10}
>>> ...

Я рад сообщить, что вышесказанное больше не работает на любой версии Python 3.

0 голосов
/ 31 мая 2019

Для Python 3:

from collections import ChainMap
a = {"a": 1, "b":2}
b = {"c":5, "d":8}
dict(ChainMap(a,b))  # {"a":1, "b":2, "c":5, "d":8}

Если у вас один и тот же ключ в обоих словарях, ChanMap будет использовать значение первого ключа и игнорирует значение второго ключа. Ура! * * 1004

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...