Jak scalić dwa słowniki w jednym wyrażeniu w Pythonie?

4781

Mam dwa słowniki w języku Python i chcę napisać jedno wyrażenie, które zwróci te dwa słowniki połączone. Potrzebowałbym tej update()metody, gdyby zwrócił wynik zamiast modyfikować słownik w miejscu.

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

Jak mogę uzyskać ten ostatni połączony słownik z, a nie x?

(Żeby być bardziej klarownym, dict.update()szukam też rozwiązania konfliktu na ostatnim miejscu ).

Carl Meyer
źródło
Przy małej szansie, że używasz wersji Python 3.9 alfa, po prostu użyjz = x | y
The Daleks

Odpowiedzi:

5687

Jak mogę połączyć dwa słowniki Python w jednym wyrażeniu?

Słowników xi y, zstaje się płytko połączone słownik o wartości od yzastą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 yzajmą drugie miejsce, a ich wartości zastąpią xwartości, a zatem 'b'wskażą na 3nasz 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 ado g:

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

i pary wartość klucza w gweźmie górę nad dicts ado 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_itemsobiekty 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 dictkonstruktora 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. dictzł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ż copya updatelub 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

Aaron Hall
źródło
9
@MohammadAzim „tylko ciągi” dotyczy tylko interpretacji argumentów słów kluczowych w wywołaniach, a nie ogólnej składni rozpakowywania. Aby wykazać, że to działa: {**{(0, 1):2}}->{(0, 1): 2}
Aaron Hall
36
krótkie odpowiedzi jak z = {**x, **y}naprawdę mnie stymulują
pcko1 10.10.19
1
Można to zmienić po zaakceptowaniu PEP-0584. Zaimplementowany zostanie nowy operator związku o następującej składni:x | y
Callam Delaney
2
Gdy odpowiedź wymaga podsumowania u góry, jest za długa.
Gringo Suave
2
Cześć, góra jest podsumowaniem, tak. Zależy od Ciebie. Całość byłaby świetnym postem na blogu. Uwaga Py 3.4 i poniżej to EOL, 3.5 zbliża się do EOL w latach 2020-09.
Gringo Suave
1616

W twoim przypadku możesz:

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

Spowoduje to, jak chcesz, wstawienie ostatniego dykta zi sprawi, że wartość klucza bzostanie poprawnie zastąpiona wartością drugiego ( y) dykta:

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

Jeśli używasz Python 3, jest to tylko trochę bardziej skomplikowane. Aby utworzyć z:

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

Jeśli używasz języka Python w wersji 3.9.0a4 lub nowszej, możesz bezpośrednio użyć:

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

Output: {'a': 1, 'c': 11, 'b': 10}
Thomas Vander Stichele
źródło
1
Nie używaj tego, ponieważ jest to bardzo nieefektywne. (Zobacz wyniki timeit poniżej.) Może być konieczne w dniach Py2, jeśli funkcja opakowania nie była opcją, ale te dni już minęły.
Gringo Suave
633

Alternatywa:

z = x.copy()
z.update(y)
Matthew Schinckel
źródło
83
Aby wyjaśnić, dlaczego nie spełnia kryteriów podanych w pytaniu: nie jest to pojedyncze wyrażenie i nie zwraca z.
Alex
2
@neuronet każdy oneliner zwykle przenosi kod, który musi się zdarzyć, do innego komponentu i tam go rozwiązuje. to zdecydowanie jeden z przypadków. ale inne języki mają do tego ładniejsze konstrukcje niż python. a posiadanie referencyjnie przezroczystego wariantu, który zwraca ten element, jest przyjemne.
Alex,
12
Ujmij to w następujący sposób: jeśli musisz umieścić dwa wiersze komentarza wyjaśniające jeden wiersz kodu osobom, którym przekazujesz kod ... czy naprawdę zrobiłeś to w jednym wierszu? :) W pełni się zgadzam, że Python nie jest do tego odpowiedni: powinien być o wiele łatwiejszy sposób. Chociaż ta odpowiedź jest bardziej pytoniczna, czy naprawdę jest tak wyraźna czy jasna? Updatenie jest jedną z podstawowych funkcji, z których ludzie często korzystają.
eric,
Cóż, jeśli ludzie nalegają, aby uczynić go oneliner, zawsze możesz to zrobić (lambda z: z.update(y) or z)(x.copy()): P
towr
340

Kolejna, bardziej zwięzła opcja:

z = dict(x, **y)

Uwaga : stała się popularną odpowiedzią, ale ważne jest, aby zaznaczyć, że jeśli yma jakieś klucze nieciągłe, fakt, że to w ogóle działa, jest nadużyciem szczegółów implementacji CPython i nie działa w Pythonie 3, lub w PyPy, IronPython lub Jython. Ponadto Guido nie jest fanem . Nie mogę więc polecić tej techniki przenośnemu kodowi zgodnemu z przyszłym kodem lub implementacji krzyżowej, co naprawdę oznacza, że ​​należy go całkowicie unikać.

Carl Meyer
źródło
Działa dobrze w Python 3 oraz PyPy i PyPy 3 , nie może rozmawiać z Jython ani Iron. Biorąc pod uwagę, że ten wzorzec jest wyraźnie udokumentowany (patrz trzeci formularz konstruktora w tej dokumentacji) twierdzę, że nie jest to „szczegół implementacji”, ale celowe użycie funkcji.
amcgregor
5
@amcgregor Przegapiłeś frazę kluczową „jeśli y ma klucze nie łańcuchowe”. To nie działa w Python3; fakt, że działa w CPython 2, jest szczegółem implementacji, na którym nie można polegać. IFF wszystkie twoje klucze są ciągami, jest to w pełni obsługiwana opcja.
Carl Meyer,
214

To prawdopodobnie nie będzie popularna odpowiedź, ale prawie na pewno nie chcesz tego robić. Jeśli chcesz kopii, która jest scaleniem, użyj kopiowania (lub głębokiej kopii , w zależności od tego, czego chcesz), a następnie zaktualizuj. Dwa wiersze kodu są znacznie bardziej czytelne - bardziej Pythonic - niż tworzenie pojedynczych wierszy za pomocą .items () + .items (). Jawne jest lepsze niż niejawne.

Ponadto, korzystając z .items () (wcześniejszych niż Python 3.0), tworzysz nową listę zawierającą elementy ze słownika. Jeśli twoje słowniki są duże, jest to dość dużo narzutu (dwie duże listy, które zostaną wyrzucone, gdy tylko powstanie scalony słownik). update () może działać wydajniej, ponieważ może przejść przez drugi dyktat element po elemencie.

Pod względem czasu :

>>> 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 małe spowolnienie między dwoma pierwszymi jest tego warte dla czytelności. Ponadto argumenty słów kluczowych do tworzenia słownika zostały dodane tylko w Pythonie 2.3, natomiast copy () i update () będą działać w starszych wersjach.

Tony Meyer
źródło
150

W odpowiedzi uzupełniającej zapytałeś o względną skuteczność tych dwóch alternatyw:

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

Na mojej maszynie przynajmniej (dość zwyczajny x86_64 z uruchomionym Pythonem 2.5.2) alternatywa z2jest nie tylko krótsza i prostsza, ale także znacznie szybsza. Możesz to samodzielnie sprawdzić za pomocą timeitmodułu dostarczonego z Pythonem.

Przykład 1: identyczne słowniki odwzorowujące do siebie 20 kolejnych liczb całkowitych:

% 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

z2wygrywa około 3,5 razy. Różne słowniki wydają się przynosić całkiem odmienne wyniki, ale z2zawsze wydają się wychodzić z przodu. (Jeśli otrzymujesz niespójne wyniki dla tego samego testu, spróbuj podać -rliczbę większą niż domyślna 3.)

Przykład 2: nienakładające się słowniki odwzorowujące 252 krótkie ciągi znaków na liczby całkowite i odwrotnie:

% 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 wygrywa około 10 razy. To całkiem spore zwycięstwo w mojej książce!

Po porównaniu tych dwóch zastanawiałem się, czy z1słabą wydajność można przypisać narzutowi związanemu z budowaniem dwóch list przedmiotów, co z kolei skłoniło mnie do zastanowienia się, czy ta odmiana mogłaby działać lepiej:

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

Kilka szybkich testów, np

% 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

doprowadzić mnie do wniosku, że z3jest nieco szybszy niż z1, ale nie tak szybki jak z2. Zdecydowanie nie warte wszystkich dodatkowych pisania.

W tej dyskusji wciąż brakuje czegoś ważnego, jakim jest porównanie wydajności tych alternatyw z „oczywistym” sposobem łączenia dwóch list: za pomocą updatemetody. Aby spróbować zachować równowagę z wyrażeniami, z których żadne nie modyfikuje x ani y, zamierzam utworzyć kopię x zamiast modyfikować ją w miejscu, w następujący sposób:

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

Typowy wynik:

% 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

Innymi słowy, z0i z2wydają się mieć zasadniczo identyczną wydajność. Czy uważasz, że to może być zbieg okoliczności? Ja nie....

W rzeczywistości posunąłbym się nawet do stwierdzenia, że ​​czysty kod Pythona nie jest w stanie zrobić nic lepszego. A jeśli możesz zrobić znacznie lepiej w module rozszerzenia C, wyobrażam sobie, że ludzie Pythona mogliby być zainteresowani włączeniem twojego kodu (lub zmiany twojego podejścia) do rdzenia Pythona. Python używa dictw wielu miejscach; optymalizacja operacji to wielka sprawa.

Możesz również napisać to jako

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

tak jak Tony, ale (co nie jest zaskakujące) różnica w notacji nie ma żadnego mierzalnego wpływu na wydajność. Użyj tego, co najbardziej Ci odpowiada. Oczywiście ma absolutną rację, wskazując, że wersja dwuskładnikowa jest znacznie łatwiejsza do zrozumienia.

zaphod
źródło
5
To nie działa w Pythonie 3; items()nie jest do zaakceptowania i iteritemsnie istnieje.
Antti Haapala
127

W Pythonie 3.0 i nowszychcollections.ChainMap wersjach możesz użyć grupowania wielu nagrań lub innych mapowań, aby utworzyć jeden, aktualizowany widok:

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

Aktualizacja dla Python 3.5 i nowszych : Możesz używać rozszerzonego pakowania i rozpakowywania słownika PEP 448 . Jest to szybkie i łatwe:

>>> x = {'a':1, 'b': 2}
>>> y = y = {'b':10, 'c': 11}
>>> {**x, **y}
{'a': 1, 'b': 10, 'c': 11}
Raymond Hettinger
źródło
3
Ale należy zachować ostrożność podczas korzystania z ChainMap. Istnieje pewien haczyk, że jeśli masz zduplikowane klucze, wartości z pierwszego mapowania zostaną wykorzystane, a gdy wywołasz a, delpowiedz, że ChainMap c usunie pierwsze mapowanie tego klucza.
Slayer
7
@Prerit Czego jeszcze można się spodziewać? To normalny sposób działania łańcuchowych przestrzeni nazw. Zastanów się, jak $ PATH działa w bash. Usunięcie pliku wykonywalnego na ścieżce nie wyklucza innego pliku wykonywalnego o tej samej nazwie w dalszej części łańcucha.
Raymond Hettinger
2
@Raymond Hettinger Zgadzam się, dodałem tylko ostrzeżenie. Większość ludzi może o tym nie wiedzieć. : D
Slayer
@Prerit Możesz rzucić, aby dicttego uniknąć, tj .:dict(ChainMap({}, y, x))
wjandrea
113

Chciałem czegoś podobnego, ale z możliwością określenia, w jaki sposób wartości na duplikatach kluczy zostały scalone, więc zhackowałem to (ale nie przetestowałem go zbyt mocno). Oczywiście nie jest to pojedyncze wyrażenie, ale pojedyncze wywołanie funkcji.

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
rcreswick
źródło
88

Rekurencyjnie / głęboka aktualizacja nagrania

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

Demonstracja:

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

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

print deepupdate(pluto_original, pluto_update)

Wyjścia:

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

Dzięki rednaw za zmiany.

Stan
źródło
1
To nie odpowiada na pytanie. Pytanie wyraźnie wymaga nowego słownika, z, z oryginalnych słowników, x i y, z wartościami z zastępującymi wartości z x - nie zaktualizowanego słownika. Ta odpowiedź modyfikuje y na miejscu, dodając wartości z x. Co gorsza, nie kopiuje tych wartości, więc można dalej modyfikować zmodyfikowany słownik, y, a modyfikacje mogą być odzwierciedlone w słowniku x. @ Jérôme Mam nadzieję, że ten kod nie powoduje żadnych błędów w Twojej aplikacji - przynajmniej rozważ użycie kopiowania głębokiego do skopiowania wartości.
Aaron Hall
1
@AaronHall zgodził się, że to nie odpowiada na pytanie. Ale to odpowiada na moje potrzeby. Rozumiem te ograniczenia, ale to nie jest problem w moim przypadku. Myśląc o tym, być może nazwa jest myląca, ponieważ może wywołać głęboką kopię, której nie zapewnia. Ale dotyczy głębokiego zagnieżdżania. Oto kolejna implementacja z Martellibot: stackoverflow.com/questions/3232943/… .
Jérôme,
72

Najlepszą wersją, jaką mogłem pomyśleć, nie używając kopii, byłoby:

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

Jest szybszy niż, dict(x.items() + y.items())ale nie tak szybki n = copy(a); n.update(b), przynajmniej na CPython. Ta wersja działa również w Pythonie 3, jeśli zmienisz iteritems()na items(), co jest automatycznie wykonywane przez narzędzie 2to3.

Osobiście najbardziej podoba mi się ta wersja, ponieważ opisuje dość dobrze to, czego chcę w pojedynczej składni funkcjonalnej. Jedynym drobnym problemem jest to, że nie jest całkowicie oczywiste, że wartości z y mają pierwszeństwo przed wartościami z x, ale nie sądzę, że trudno to rozgryźć.

driax
źródło
71

Python 3.5 (PEP 448) pozwala na ładniejszą opcję składni:

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

Lub nawet

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

W Pythonie 3.9 używasz także | i | = w poniższym przykładzie z PEP 584

d = {'spam': 1, 'eggs': 2, 'cheese': 3}
e = {'cheese': 'cheddar', 'aardvark': 'Ethel'}
d | e
# {'spam': 1, 'eggs': 2, 'cheese': 'cheddar', 'aardvark': 'Ethel'}
Bilal Syed Hussain
źródło
W jaki sposób to rozwiązanie jest lepsze niż dict(x, **y)rozwiązanie? Jak wspomniałeś (@CllMeyer) w notatce swojej własnej odpowiedzi ( stackoverflow.com/a/39858/2798610 ), Guido uważa to rozwiązanie za nielegalne .
Blackeagle52
14
Guido nie lubi dict(x, **y)(bardzo dobrego) powodu, że polega on ytylko na posiadaniu kluczy, które są poprawnymi nazwami argumentów słów kluczowych (chyba że używasz CPython 2.7, gdzie oszukiwa konstruktor dict). To zastrzeżenie / ograniczenie nie dotyczy PEP 448, który uogólnia **składnię rozpakowywania w celu dyktowania literałów. To rozwiązanie ma tę samą zwięzłość, co dict(x, **y)bez wad.
Carl Meyer,
62
x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
z = dict(x.items() + y.items())
print z

W przypadku elementów z kluczami w obu słownikach („b”) możesz kontrolować, który z nich znajdzie się na wyjściu, umieszczając go jako ostatni.

Greg Hewgill
źródło
W Pythonie 3 otrzymasz TypeError: nieobsługiwane typy operandów dla +: 'dict_items' i 'dict_items' ... powinieneś obudować każdy dict list () jak: dict (list (x.items ()) + lista (y.items ()))
powiedział
49

Chociaż na to pytanie udzielono już odpowiedzi kilka razy, to proste rozwiązanie problemu nie zostało jeszcze wymienione.

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

Jest tak szybki jak Z0 i wspomniane powyżej zło Z2, ale łatwo go zrozumieć i zmienić.

phobie
źródło
3
ale są to trzy stwierdzenia, a nie jedno wyrażenie
fortran
14
Tak! Wspomniane rozwiązania z jednym wyrażeniem są powolne lub złe. Dobry kod jest czytelny i łatwy do utrzymania. Problemem jest więc pytanie, a nie odpowiedź. Powinniśmy prosić o najlepsze rozwiązanie problemu, a nie o rozwiązanie jednoliniowe.
phobie
7
Zgub z4 = {}i zmień następny wiersz na z4 = x.copy()- lepszy niż dobry kod nie robi niepotrzebnych rzeczy (co czyni go jeszcze bardziej czytelnym i łatwym w utrzymaniu).
martineau
3
Twoja sugestia zmieniłaby to na odpowiedź Matthewsa. Chociaż jego odpowiedź jest dobra, myślę, że moja jest bardziej czytelna i łatwiejsza w utrzymaniu. Dodatkowa linia byłaby zła, gdyby kosztowała czas wykonania.
phobie
47
def dict_merge(a, b):
  c = a.copy()
  c.update(b)
  return c

new = dict_merge(old, extras)

Wśród takich podejrzanych i wątpliwych odpowiedzi, ten lśniący przykład jest jedynym i jedynym dobrym sposobem na połączenie dyktatów w Pythonie, popieranym przez samego dyktatora Guido van Rossuma ! Ktoś inny zasugerował połowę tego, ale nie nadał mu żadnej funkcji.

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

daje:

{'color': 'red', 'owner': 'Carl', 'model': 'Ferrari'}
Sam Watkins
źródło
39

Jeśli uważasz, że lambdas są złe, nie czytaj dalej. Zgodnie z życzeniem możesz napisać szybkie i efektywne pod względem pamięci rozwiązanie z jednym wyrażeniem:

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}

Jak sugerowano powyżej, użycie dwóch linii lub napisanie funkcji jest prawdopodobnie lepszym sposobem.

EMS
źródło
33

Bądź pytoniczny. Użyj zrozumienia :

z={i:d[i] for d in [x,y] for i in d}

>>> print z
{'a': 1, 'c': 11, 'b': 10}
Robino
źródło
1
Jako funkcja:def dictmerge(*args): return {i:d[i] for d in args for i in d}
jessexknight
1
Zapisz wyszukiwanie, powtarzając bezpośrednio pary klucz / wartość:z={k: v for d in (x, y) for k, v in d.items()}
ShadowRanger
30

W python3 itemsmetoda nie zwraca już listy , ale widok , który działa jak zestaw. W takim przypadku musisz wziąć ustawiony związek, ponieważ konkatenacja z +nie będzie działać:

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

W przypadku zachowania podobnego do python3 w wersji 2.7 viewitemsmetoda powinna działać zamiast items:

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

W każdym razie wolę ten zapis, ponieważ bardziej naturalne wydaje się myślenie o nim jako o określonej operacji związkowej niż o konkatenacji (jak pokazuje tytuł).

Edytować:

Jeszcze kilka punktów dla Pythona 3. Po pierwsze, zauważ, że dict(x, **y)sztuczka nie będzie działać w Pythonie 3, chyba że klucze w ysą ciągami.

Ponadto odpowiedź Chainmap Raymonda Hettingera jest dość elegancka, ponieważ może przyjmować dowolną liczbę dykt jako argumentów, ale z dokumentów wygląda tak, jakby sekwencyjnie przeglądała listę wszystkich dykt dla każdego wyszukiwania:

Wyszukiwanie przeszukuje podstawowe mapowania sukcesywnie, aż do znalezienia klucza.

Może to spowolnić działanie, jeśli masz dużo wyszukiwań w swojej aplikacji:

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

A więc o rząd wielkości wolniej dla odnośników. Jestem fanem Chainmap, ale wygląda mniej praktycznie tam, gdzie może być wiele odnośników.

broda
źródło
22

Nadużycie prowadzące do jednoprecyzyjnego rozwiązania dla odpowiedzi Matthew :

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

Powiedziałeś, że chcesz jednego wyrażenia, więc nadużyłem lambdawiązania nazwy i krotek, aby zastąpić limit jednego wyrażenia lambdy. Nie krępuj się.

Możesz to oczywiście zrobić, jeśli nie chcesz kopiować:

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> z = (x.update(y), x)[1]
>>> z
{'a': 1, 'b': 10, 'c': 11}
Claudiu
źródło
22

Proste rozwiązanie za pomocą itertools, które zachowuje porządek (ostatnie dykty mają pierwszeństwo)

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

I to jest użycie:

>>> 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}
reubano
źródło
16

Mimo że odpowiedzi były dobre dla tego płytkiego słownika, żadna z zdefiniowanych tutaj metod nie wykonuje głębokiego scalenia słownika.

Przykłady:

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

Można się spodziewać czegoś takiego:

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

Zamiast tego otrzymujemy:

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

Wpis „one” powinien mieć w swym słowniku elementy „depth_2” i „extra”, jeśli naprawdę był to scalenie.

Korzystanie z łańcucha również nie działa:

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

Prowadzi do:

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

Głębokie scalenie, które wykonał rcwesick, również daje ten sam rezultat.

Tak, będzie działać, aby scalić przykładowe słowniki, ale żaden z nich nie jest ogólnym mechanizmem scalania. Zaktualizuję to później, gdy napiszę metodę, która dokonuje prawdziwego scalenia.

Thanh Lim
źródło
11

(Tylko dla Python2.7 *; istnieją prostsze rozwiązania dla Python3 *.)

Jeśli nie masz nic przeciwko importowaniu standardowego modułu biblioteki, możesz to zrobić

from functools import reduce

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

( or aTrochę w lambdajest konieczne, ponieważ dict.updatezawsze powraca Nonepo sukcesie).

kjo
źródło
11

Jeśli nie masz nic przeciwko mutowaniu x,

x.update(y) or x

Prosty, czytelny, wydajny. Wiesz, że update() zawsze zwraca None, co jest wartością fałszywą. Tak więc powyższe wyrażenie zawsze będzie oceniać xpo aktualizacji.

Metody mutacji w standardowej bibliotece (podobnie jak .update()) zwracane są Nonezgodnie z konwencją, więc ten wzór będzie działał również na nich. Jeśli używasz metody niezgodnej z tą konwencją, ormoże nie działać. Ale zamiast tego możesz użyć wyświetlania krotek i indeksu, aby uczynić je pojedynczym wyrażeniem. Działa to niezależnie od tego, co ocenia pierwszy element.

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

Jeśli nie masz xjeszcze zmiennej, możesz użyć lambdado utworzenia lokalnego bez użycia instrukcji przypisania. Sprowadza się to do użycia lambdajako wyrażenia let , co jest powszechną techniką w językach funkcjonalnych, ale może nie jest mityczne.

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

Chociaż nie różni się tak bardzo od następującego zastosowania nowego operatora morsa (tylko Python 3.8+):

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

Jeśli potrzebujesz kopii, styl PEP 448 jest najłatwiejszy {**x, **y}. Ale jeśli nie jest to dostępne w (starszej) wersji Pythona, wzór let również tutaj działa.

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

(Jest to oczywiście równoważne (z := x.copy()).update(y) or z, ale jeśli twoja wersja Pythona jest wystarczająco nowa, styl PEP 448 będzie dostępny.)

gilch
źródło
10

Opierając się na pomysłach tu i gdzie indziej, zrozumiałem funkcję:

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

Użycie (testowane w Pythonie 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})

Zamiast tego możesz użyć lambda.

Bijou Trouvaille
źródło
10

Problem, który mam z dotychczas wymienionymi rozwiązaniami, polega na tym, że w połączonym słowniku wartość klucza „b” wynosi 10, ale moim zdaniem powinna to być wartość 12. W tym świetle przedstawiam:

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

Wyniki:

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}
upandacross
źródło
1
Możesz być zainteresowany cytoolz.merge_with( toolz.readthedocs.io/en/latest/… )
bli
10

Jest tak głupie, że .updatenic nie zwraca.
Po prostu używam prostej funkcji pomocnika, aby rozwiązać problem:

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

Przykłady:

merge(dict1,dict2)
merge(dict1,dict2,dict3)
merge(dict1,dict2,dict3,dict4)
merge({},dict1,dict2)  # this one returns a new copy
Uwolnić się
źródło
10
from collections import Counter
dict1 = {'a':1, 'b': 2}
dict2 = {'b':10, 'c': 11}
result = dict(Counter(dict1) + Counter(dict2))

To powinno rozwiązać twój problem.

reetesh11
źródło
9

Można to zrobić za pomocą pojedynczego rozumienia:

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

Moim zdaniem najlepsza odpowiedź na część „pojedyncze wyrażenie”, ponieważ nie są potrzebne żadne dodatkowe funkcje, i jest krótka.

RemcoGerlich
źródło
Podejrzewam, że wydajność nie będzie zbyt dobra; utworzenie zestawu każdego dykta, a następnie tylko iteracja po klawiszach oznacza kolejne wyszukiwanie wartości za każdym razem (choć stosunkowo szybkie, wciąż zwiększa kolejność funkcji skalowania)
Breezer
2
wszystko zależy od używanej wersji Pythona. W wersji 3.5 i nowszej {** x, ** y} podaje konkatenowany słownik
Rashid Mv
9

Pojawi się nowa opcja, kiedy wydania Python 3.8 ( zaplanowane na 20 października 2019 r. ), Dzięki PEP 572: Wyrażenia przypisania . Nowy operator wyrażeń przypisania :=pozwala przypisać wynik copyi nadal używać go do wywoływania update, pozostawiając połączony kod pojedynczym wyrażeniem zamiast dwóch instrukcji, zmieniając:

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

do:

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

zachowując się jednakowo pod każdym względem. Jeśli musisz również zwrócić wynikowy dict(poprosiłeś o wyrażenie zwracające dict; powyższe tworzy i przypisuje je newdict, ale nie zwraca, więc nie możesz użyć go do przekazania argumentu do funkcji w obecnej postaci myfunc((newdict := dict1.copy()).update(dict2))) , a następnie po prostu dodaj or newdictna końcu (ponieważ updatezwraca None, co jest fałszem, będzie wówczas oceniać i zwracać newdictjako wynik wyrażenia):

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

Ważne zastrzeżenie: ogólnie odradzam takie podejście na rzecz:

newdict = {**dict1, **dict2}

Podejście do rozpakowywania jest jaśniejsze (dla każdego, kto wie o uogólnionym rozpakowaniu w pierwszej kolejności, co powinieneś ), w ogóle nie wymaga nazwy wyniku (więc jest o wiele bardziej zwięzłe przy tworzeniu tymczasowego, który jest natychmiast przekazywany do funkcjonuje lub jest zawarty w list/ tupledosłownym lub podobnym) i jest prawie na pewno także szybszy, będąc (na CPython) w przybliżeniu równoważnym z:

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

ale zrobiono to w warstwie C, używając konkretnego dictinterfejsu API, więc nie jest związane z dynamicznym wyszukiwaniem / wiązaniem metody ani obciążeniem wywołania funkcji(newdict := dict1.copy()).update(dict2) jest nieunikniony identyczny z oryginalnym dwóch tulei w zachowaniu, wykonując prace w oddzielnych etapach, z dynamicznym odnośnika / bindowanie / wywoływanie metod.

Jest również bardziej rozszerzalny, ponieważ połączenie trzech dicts jest oczywiste:

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

gdzie użycie wyrażeń przypisania nie będzie tak skalowane; najbliższy byłby to:

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

lub bez tymczasowej krotki Nones, ale z testowaniem prawdziwości każdego Nonewyniku:

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

jeden z co jest oczywiście dużo mniej efektowna, a ponadto zawiera niewydolności (albo zmarnowany czasowy tupleod NoneS do oddzielania przecinkiem czy niepotrzebnych testów truthiness każdego update„S Nonepowrotu do orrozdzielenia).

Jedyną prawdziwą zaletą podejścia do wyrażania przypisań jest:

  1. Masz kod rodzajowy, że potrzeby obsługiwać zarówno setS i dictS (obie z nich obsługuje copyi updatetak kod działa mniej więcej jak chcesz go do spodziewać)
  2. Oczekujesz, że otrzymasz dowolne obiekty przypominające dykt , nie tylko dictsam, i musisz zachować typ i semantykę lewej strony (zamiast kończyć się zwykłym dict). Chociaż myspecialdict({**speciala, **specialb})może działać, wymagałoby to dodatkowego tymczasowego działania dict, a jeśli myspecialdictma funkcje, których zwykły dictnie może zachować (np. Zwykłe dicts teraz zachowują kolejność na podstawie pierwszego pojawienia się klucza, a wartość na podstawie ostatniego pojawienia się klucza; taki, który zachowuje porządek na podstawie ostatniego pojawienie się klucza, więc aktualizacja wartości przesuwa go również do końca), to semantyka byłaby błędna. Ponieważ wersja wyrażenia przypisania korzysta z nazwanych metod (które są prawdopodobnie przeciążone, aby się odpowiednio zachowywać), nigdy nie tworzy dictw ogóle (chyba że dict1było to a dict), zachowując typ oryginalny (i semantykę typu oryginalnego), a wszystko to przy jednoczesnym unikaniu tymczasowości.
ShadowRanger
źródło
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}
John La Rooy
źródło
Ta metoda zastępuje xswoją kopię. Jeśli xjest argumentem funkcji, to nie zadziała (patrz przykład )
bartolo-otrit