Jak mogę połączyć dwa słowniki Python w jednym wyrażeniu?
Słowników x
i y
, z
staje się płytko połączone słownik o wartości od y
zastąpienie tych z x
.
W Pythonie 3.5 lub nowszym:
z = {**x, **y}
W Pythonie 2 (lub 3.4 lub niższym) napisz funkcję:
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
i teraz:
z = merge_two_dicts(x, y)
W Pythonie 3.9.0a4 lub większej (ostatecznej daty wydania ok października 2020): PEP-584 , omawiane tutaj , został wdrożony w celu dalszego uproszczenia to:
z = x | y # NOTE: 3.9+ ONLY
Wyjaśnienie
Załóżmy, że masz dwa dykta i chcesz połączyć je w nowy dykta bez zmiany oryginalnych dykt:
x = {'a': 1, 'b': 2}
y = {'b': 3, 'c': 4}
Pożądanym rezultatem jest uzyskanie nowego słownika ( z
) z połączonymi wartościami, a wartości drugiego dyktanda zastąpią wartości z pierwszego.
>>> z
{'a': 1, 'b': 3, 'c': 4}
Nowa składnia tego, zaproponowana w PEP 448 i dostępna od wersji Python 3.5 , to
z = {**x, **y}
I to jest rzeczywiście pojedyncze wyrażenie.
Zauważ, że możemy również połączyć się z literalną notacją:
z = {**x, 'foo': 1, 'bar': 2, **y}
i teraz:
>>> z
{'a': 1, 'b': 3, 'foo': 1, 'bar': 2, 'c': 4}
Jest teraz wyświetlany jako zaimplementowany w harmonogramie wydań dla wersji 3.5, PEP 478 , a teraz znalazł się w dokumencie Co nowego w dokumencie Python 3.5 .
Ponieważ jednak wiele organizacji nadal korzysta z języka Python 2, możesz chcieć to zrobić w sposób zgodny z poprzednimi wersjami. Klasycznym sposobem Pythona, dostępnym w Python 2 i Python 3.0-3.4, jest zrobienie tego jako dwuetapowy proces:
z = x.copy()
z.update(y) # which returns None since it mutates z
W obu podejściach y
zajmą drugie miejsce, a ich wartości zastąpią x
wartości, a zatem 'b'
wskażą na 3
nasz wynik końcowy.
Jeszcze nie w Pythonie 3.5, ale chcesz mieć jedno wyrażenie
Jeśli nie korzystasz jeszcze z języka Python 3.5 lub potrzebujesz napisać kod kompatybilny wstecz, a chcesz tego w jednym wyrażeniu , najbardziej wydajnym i poprawnym podejściem jest umieszczenie go w funkcji:
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
a następnie masz jedno wyrażenie:
z = merge_two_dicts(x, y)
Możesz także utworzyć funkcję scalającą nieokreśloną liczbę nagrań, od zera do bardzo dużej liczby:
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
Ta funkcja będzie działać w Pythonie 2 i 3 dla wszystkich nagrań. np. podane dykty a
do g
:
z = merge_dicts(a, b, c, d, e, f, g)
i pary wartość klucza w g
weźmie górę nad dicts a
do f
, i tak dalej.
Krytyka innych odpowiedzi
Nie używaj tego, co widzisz w wcześniej zaakceptowanej odpowiedzi:
z = dict(x.items() + y.items())
W Pythonie 2 tworzysz dwie listy w pamięci dla każdego nagrania, tworzysz trzecią listę w pamięci o długości równej długości pierwszych dwóch razem, a następnie odrzucasz wszystkie trzy listy, aby utworzyć dykt. W Pythonie 3 to się nie powiedzie, ponieważ dodajesz dwa dict_items
obiekty razem, a nie dwie listy -
>>> 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'
i musicie je jawnie utworzyć jako listy, np z = dict(list(x.items()) + list(y.items()))
. Jest to marnotrawstwo zasobów i mocy obliczeniowej.
Podobnie wzięcie unii items()
w Pythonie 3 ( viewitems()
w Pythonie 2.7) również nie powiedzie się, gdy wartości są obiektami nieukończonymi (np. Listami). Nawet jeśli twoje wartości są haszowalne, ponieważ zestawy są semantycznie nieuporządkowane, zachowanie jest niezdefiniowane pod względem pierwszeństwa. Więc nie rób tego:
>>> c = dict(a.items() | b.items())
Ten przykład pokazuje, co się dzieje, gdy wartości są nie do zniesienia:
>>> x = {'a': []}
>>> y = {'b': []}
>>> dict(x.items() | y.items())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
Oto przykład, w którym y powinno mieć pierwszeństwo, ale zamiast tego wartość z x jest zachowywana z powodu dowolnej kolejności zbiorów:
>>> x = {'a': 2}
>>> y = {'a': 1}
>>> dict(x.items() | y.items())
{'a': 2}
Kolejny hack, którego nie powinieneś używać:
z = dict(x, **y)
Korzysta z dict
konstruktora i jest bardzo szybki i efektywny pod względem pamięci (nawet nieco bardziej niż w naszym dwuetapowym procesie), ale chyba, że wiesz dokładnie, co się tutaj dzieje (to znaczy drugi dykt jest przekazywany jako argumenty słów kluczowych do dyktatora konstruktor), jest trudny do odczytania, nie jest to zamierzone użycie, a więc nie jest Pythonic.
Oto przykład zastosowania korygowanego w programie django .
Dicts są przeznaczone do pobierania kluczy haszujących (np. Frozensets lub krotek), ale ta metoda kończy się niepowodzeniem w Pythonie 3, gdy klucze nie są łańcuchami.
>>> c = dict(a, **b)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: keyword arguments must be strings
Z listy mailingowej Guido van Rossum, twórca języka, napisał:
Nie mam nic przeciwko stwierdzeniu, że dict ({}, ** {1: 3}) jest nielegalny, ponieważ w końcu jest to nadużycie mechanizmu **.
i
Najwyraźniej dict (x, ** y) krąży wokół jako „fajny hack” dla „wywołaj x.update (y) i zwróć x”. Osobiście uważam, że jest to bardziej podłe niż fajne.
Rozumiem (podobnie jak rozumie twórcę języka ), że zamierzonym zastosowaniem dict(**y)
jest tworzenie dykt dla celów czytelności, np .:
dict(a=1, b=10, c=11)
zamiast
{'a': 1, 'b': 10, 'c': 11}
Odpowiedź na komentarze
Pomimo tego, co mówi Guido, dict(x, **y)
jest zgodny ze specyfikacją dyktatora, która btw. działa zarówno w Pythonie 2, jak i 3. Fakt, że działa to tylko w przypadku kluczy łańcuchowych, jest bezpośrednią konsekwencją działania parametrów słów kluczowych, a nie krótkim dict. Również użycie operatora ** w tym miejscu nie stanowi nadużycia mechanizmu, w rzeczywistości ** został zaprojektowany właśnie do przekazywania nagrań jako słów kluczowych.
Ponownie, nie działa dla 3, gdy klucze nie są łańcuchami. Implikowana umowa wywoływania polega na tym, że przestrzenie nazw przyjmują zwykłe dyktanda, podczas gdy użytkownicy muszą przekazywać tylko argumenty słów kluczowych, które są ciągami znaków. Wymuszały to wszystkie inne kallaby. dict
złamał tę spójność w Pythonie 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}
Ta niespójność była zła, biorąc pod uwagę inne implementacje Pythona (Pypy, Jython, IronPython). Dlatego zostało to naprawione w Pythonie 3, ponieważ użycie to może być przełomową zmianą.
Oświadczam, że umyślne pisanie kodu, który działa tylko w jednej wersji języka lub działa tylko z pewnymi arbitralnymi ograniczeniami, jest złośliwe.
Więcej komentarzy:
dict(x.items() + y.items())
jest nadal najbardziej czytelnym rozwiązaniem dla Pythona 2. Czytelność się liczy.
Moja odpowiedź: merge_two_dicts(x, y)
właściwie wydaje mi się znacznie jaśniejsza, jeśli naprawdę martwimy się o czytelność. I nie jest kompatybilny do przodu, ponieważ Python 2 jest coraz bardziej przestarzały.
{**x, **y}
nie obsługuje zagnieżdżonych słowników. zawartość zagnieżdżonych kluczy jest po prostu nadpisywana, a nie scalana [...] Skończyło się na tym, że te odpowiedzi, które nie łączą się rekurencyjnie, zostały spalone i byłem zaskoczony, że nikt o tym nie wspominał. W mojej interpretacji słowa „łączenie” odpowiedzi te opisują „aktualizowanie jednego dykta z innym”, a nie łączenie.
Tak. Muszę odesłać cię z powrotem do pytania, które dotyczy płytkiego połączenia dwóch słowników, przy czym pierwsze wartości są zastępowane przez drugie - jednym wyrażeniem.
Zakładając dwa słowniki, można rekurencyjnie łączyć je w jedną funkcję, ale należy uważać, aby nie modyfikować nagrań z żadnego źródła, a najpewniejszym sposobem uniknięcia tego jest zrobienie kopii podczas przypisywania wartości. Ponieważ klucze muszą być haszowalne i dlatego zwykle są niezmienne, ich kopiowanie nie ma sensu:
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
Stosowanie:
>>> x = {'a':{1:{}}, 'b': {2:{}}}
>>> y = {'b':{10:{}}, 'c': {11:{}}}
>>> dict_of_dicts_merge(x, y)
{'b': {2: {}, 10: {}}, 'a': {1: {}}, 'c': {11: {}}}
Wymyślenie nieprzewidzianych okoliczności dla innych typów wartości wykracza daleko poza zakres tego pytania, dlatego wskażę moją odpowiedź na pytanie kanoniczne dotyczące „Scalenia słowników” .
Mniej wydajne, ale poprawne ad-hoki
Podejścia te są mniej wydajne, ale zapewnią prawidłowe zachowanie. Będą one znacznie mniej wydajnych niż copy
a update
lub nowy rozpakowanie ponieważ iterację każdej pary klucz-wartość na wyższym poziomie abstrakcji, ale zrobić respektować porządek pierwszeństwa (ostatnie dicts mają pierwszeństwo)
Można również ręcznie połączyć łańcuchy w ramach rozumienia:
{k: v for d in dicts for k, v in d.items()} # iteritems in Python 2.7
lub w Pythonie 2.6 (i być może już w wersji 2.4, kiedy wprowadzono wyrażenia generatora):
dict((k, v) for d in dicts for k, v in d.items())
itertools.chain
połączy iteratory z parami klucz-wartość we właściwej kolejności:
import itertools
z = dict(itertools.chain(x.iteritems(), y.iteritems()))
Analiza wydajności
Zamierzam tylko przeprowadzić analizę wydajności, o której wiadomo, że zachowuje się poprawnie.
import timeit
Poniższe czynności są wykonywane w systemie Ubuntu 14.04
W Python 2.7 (systemowy 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
W 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
Zasoby dotyczące słowników
z = x | y