Pytanie Jak połączyć dwa słowniki w jednym wyrażeniu?


Mam dwa słowniki w języku Python i chcę napisać jedno wyrażenie, które zwraca te dwa słowniki, połączy się. The update() metoda byłaby tym, czego potrzebuję, gdyby zwracał swój wynik zamiast modyfikować dyk na 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 końcowy scalony dyktat w z, nie x?

(Aby być bardziej precyzyjnym, ostatnia z nich wygrywa obsługę konfliktów dict.update() jest też tym, czego szukam.)


3222
2017-09-02 07:44


pochodzenie




Odpowiedzi:


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

Dla słowników x i y, z staje się połączonym słownikiem z wartościami z y zastępując je x.

  • W języku Python 3.5 lub nowszym:

    z = {**x, **y}
    w = {'foo': 'bar', 'baz': 'qux', **y}  # merge a dict with literal values
    
  • 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

    z = merge_two_dicts(x, y)
    

Wyjaśnienie

Załóżmy, że masz dwa dyktury i chcesz połączyć je w nowy dyktat bez zmieniania oryginalnych dyktatur:

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

Pożądanym rezultatem jest uzyskanie nowego słownika (z) z wartościami scalonymi i wartościami drugiego dicta nadpisującymi wartości z pierwszego.

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

Nowa składnia tego, zaproponowana w PEP 448 i dostępny od Pythona 3.5, jest

z = {**x, **y}

I rzeczywiście jest to pojedyncze wyrażenie. Jest teraz wyświetlane jako zaimplementowane w harmonogram wydania dla wersji 3.5, PEP 478i teraz się w to wkracza Co nowego w Pythonie 3.5 dokument.

Jednakże, ponieważ wiele organizacji wciąż jest w Pythonie 2, możesz chcieć to zrobić w sposób kompatybilny wstecz. Klasyczna metoda Pythonona, dostępna w Pythonie 2 i Pythonie 3.0-3.4, polega na wykonaniu dwuetapowego procesu:

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

W obu podejściach y zajmie drugie miejsce, a jego wartości zostaną zastąpione xw ten sposób wartości 'b' wskaże 3 w naszym ostatecznym wyniku.

Jeszcze nie w Pythonie 3.5, ale chcesz pojedyncze wyrażenie

Jeśli nie jesteś jeszcze w Pythonie 3.5 lub potrzebujesz napisać kod zgodny z poprzednimi wersjami i chcesz to zrobić w pojedyncze wyrażenie, najbardziej wydajnym sposobem na poprawne podejście 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ę scalania niezdefiniowanej liczby dyktów, 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 dykt. na przykład podane dyktanda a do g:

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

i pary wartości klucza w g będzie mieć pierwszeństwo przed dyktami a do f, i tak dalej.

Krytyka innych odpowiedzi

Nie używaj tego, co widzisz w poprzednio zaakceptowanej odpowiedzi:

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

W Pythonie 2 tworzysz dwie listy w pamięci dla każdego dyktatu, tworzymy trzecią listę w pamięci o długości równej długości dwóch pierwszych razem, a następnie odrzucasz wszystkie trzy listy, aby stworzyć dykt. W Pythonie 3 to się nie powiedzie ponieważ dodajesz dwa dict_items obiekty razem, 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 musiałbyś jawnie utworzyć je jako listy, np. z = dict(list(x.items()) + list(y.items())). Jest to strata zasobów i mocy obliczeniowej.

Podobnie, biorąc unii items()w Pythonie 3 (viewitems() w Pythonie 2.7) również zawiedzie, gdy wartości są nieodmiennymi obiektami (na przykład listami). Nawet jeśli twoje wartości są nieosiągalne, ponieważ zbiory są semantycznie nieuporządkowane, zachowanie jest niezdefiniowane w odniesieniu do pierwszeństwa. Więc nie rób tego:

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

Ten przykład demonstruje, co dzieje się, gdy wartości nie są wymienialne:

>>> 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 powinien mieć pierwszeństwo, ale zamiast tego wartość z x jest zachowywana z powodu arbitralnej 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)

To wykorzystuje dict konstruktor, i jest bardzo szybki i efektywny pod względem pamięci (nawet nieco bardziej - niż w naszym dwuetapowym procesie), ale jeśli nie wiesz dokładnie, co się tutaj dzieje (to znaczy, drugi dyktat jest przekazywany jako argumenty słów kluczowych do konstruktora dyktowanego), to jest trudne do odczytania, nie jest to zamierzone użycie, więc nie jest to Python.

Oto przykład użycia użytkowania remediowane w django.

Dicts są przeznaczone do użycia kluczy skrótu (np. Frozensets lub krotek), ale ta metoda kończy się niepowodzeniem w Pythonie 3, gdy klucze nie są ciągami.

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

Od Lista mailingowa, Guido van Rossum, twórca języka, napisał:

Mam się dobrze   stwierdzenie, że dict ({}, ** {1: 3}) jest nielegalne, ponieważ w końcu jest to nadużycie   Mechanizm.

i

Najwyraźniej dict (x, ** y) działa jako "cool hack" dla "call"   x.update (y) i return x ". Osobiście uważam, że jest to bardziej podłe niż   chłodny.

To jest moje zrozumienie (jak również zrozumienie twórca języka), że zamierzone użycie dla dict(**y) służy do tworzenia nagrań 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ą dict, która przy okazji. działa zarówno dla Pythona 2, jak i 3. Fakt, że działa to tylko dla kluczy łańcuchowych, jest bezpośrednią konsekwencją działania parametrów słów kluczowych, a nie krótkich czasów dict. W tym miejscu nie używa się operatora **, ponieważ został on specjalnie zaprojektowany, by przekazywać dyktatury jako słowa kluczowe.

Znowu nie działa dla 3, gdy klucze nie są łańcuchami. Niejawna umowa na wywołanie jest taka, że ​​przestrzenie nazw pobierają zwykłe dict, a użytkownicy muszą przekazywać tylko argumenty słów kluczowych, które są ciągami. Wszystkie inne kalki egzekwowały to. dict zepsuł 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 pod względem innych implementacji Pythona (Pypy, Jython, IronPython). Zostało to naprawione w Pythonie 3, ponieważ może to być przełomowa zmiana.

Twierdzę, że złośliwa niekompetencja polega na celowym pisaniu kodu działającego tylko w jednej wersji językowej lub działającego tylko pod pewnymi arbitralnymi ograniczeniami.

Kolejny komentarz:

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 rzeczywistości wydaje mi się dużo jaśniejsze, jeśli naprawdę zależy nam na czytelności. I nie jest kompatybilny w przód, ponieważ Python 2 jest coraz bardziej przestarzały.

Mniej skuteczne, ale poprawne Ad-hocs

Podejścia te są mniej wydajne, ale zapewnią prawidłowe zachowanie. Oni będą o wiele mniej wykonawca niż copy i update lub nowe rozpakowanie, ponieważ przechodzą przez każdą parę klucz-wartość na wyższym poziomie abstrakcji, ale one zrobić przestrzegać kolejności pierwszeństwa (ostatnie dyktatury mają pierwszeństwo)

Można również połączyć łańcuchy dyktowania ręcznie w trybie rozpoznawania dyktowanego:

{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, gdy wprowadzono wyrażeń generatora):

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

itertools.chain połączy iteratory po parach klucz-wartość we właściwej kolejności:

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

Analiza wydajności

Zajmę się analizą wydajności tylko tych zwyczajów, o których wiadomo, że zachowują się poprawnie.

import timeit

Poniżej przedstawiono Ubuntu 14.04

W języku Python 2.7 (system 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 Pythonie 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


3352
2017-11-10 22:11





W twoim przypadku możesz:

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

To będzie, jak chcesz, umieścić ostateczny dyktat w zi ustaw wartość dla klucza b być odpowiednio nadpisany przez drugi (y) wartość dict:

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

1440
2017-09-02 07:50





Alternatywa:

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

547
2017-09-02 13:00



Aby wyjaśnić, dlaczego nie spełnia to krytycznej oceny pytania: nie jest to pojedyncze wyrażenie i nie zwraca z. - Alex
@Alex czasami oryginalne pytanie nie jest właściwym pytaniem. Jeśli chcesz na przykład użyć potwora niezwiązanego z pytaniem regularnym, możesz zrobić coś w jednym wierszu lub dwóch eleganckich liniach. zrobić X, a następnie użyć dwóch linii. The {**x, **y} podejście w zaakceptowanej odpowiedzi jest potwornością. - neuronet
@neuronet każdy oneliner jest zwykle tylko ruchomym kodem, który musi się zdarzyć w innym komponencie i rozwiązuje go tam. jest to zdecydowanie jeden z przypadków. ale inne języki mają lepsze konstrukcje niż python. i posiadanie referencyjnie przezroczystego wariantu, który zwraca jego element, to miło mieć coś. - Alex
Mówiąc tak: jeśli chcesz umieścić dwie linijki komentarzy wyjaśniających swoją linię kodu ludziom, którym przekazujesz swój kod, aby ... naprawdę zrobiłeś to w jednym wierszu? :) W pełni zgadzam się, że Python nie jest do tego dobry: powinno być o wiele prostszy sposób. Podczas gdy ta odpowiedź jest bardziej pythonic, czy jest to naprawdę tak wyraźne lub jasne? Update nie jest jedną z "podstawowych" funkcji, z których ludzie często korzystają. - neuronet


Kolejna, bardziej zwięzła opcja:

z = dict(x, **y)

Uwaga: stało się to popularną odpowiedzią, ale ważne jest, aby zwrócić uwagę, że jeśli y ma wszelkie klucze nie będące ciągami znaków, fakt, że to działa w ogóle jest nadużyciem szczegółów implementacji CPython i nie działa w Pythonie 3, PyPy, IronPython lub Jython. Również, Guido nie jest fanem. Tak więc nie mogę polecić tej techniki kompatybilnego z przodu lub międzykontynentalnego przenośnego kodu, co naprawdę oznacza, że ​​należy jej całkowicie unikać.


273
2017-09-02 15:52





Prawdopodobnie nie będzie to popularna odpowiedź, ale prawie na pewno nie chcesz tego robić. Jeśli chcesz kopię, która jest połączeniem, użyj kopii (lub deepcopy, w zależności od tego, co chcesz), a następnie zaktualizuj. Dwa wiersze kodu są znacznie bardziej czytelne - bardziej Pythonic - niż tworzenie pojedynczej linii za pomocą .items () + .items (). Jawny jest lepszy niż niejawny.

Ponadto, jeśli używasz .items () (pre Python 3.0), tworzysz nową listę zawierającą elementy z dyktatury. Jeśli twoje słowniki są duże, oznacza to sporo wydatków (dwie duże listy, które zostaną odrzucone zaraz po utworzeniu scalonego dict). Funkcja update () może działać wydajniej, ponieważ może działać przez drugi element dict-item.

Pod względem czas:

>>> 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 warte jego czytelności. Ponadto argumenty dotyczące słów kluczowych do tworzenia słowników zostały dodane tylko w Pythonie 2.3, natomiast copy () i update () będą działały w starszych wersjach.


168
2017-09-08 11:16





W dalszej odpowiedzi zapytałeś o względną skuteczność tych dwóch możliwości:

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

Na moim komputerze przynajmniej (dość zwykły x86_64 z zainstalowanym Pythonem 2.5.2), alternatywa z2 jest nie tylko krótszy i prostszy, ale także znacznie szybszy. Możesz to sprawdzić samodzielnie, używając timeit moduł dołączony do Pythona.

Przykład 1: identyczne słowniki odwzorowujące dla 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

z2 wygrywa o współczynnik 3,5 lub więcej. Różne słowniki wydają się dawać zupełnie inne wyniki, ale z2 zawsze wydaje się wychodzić naprzód. (Jeśli uzyskasz niespójne wyniki dla podobnie przetestuj, spróbuj przekazać -r o numerze większym niż domyślny 3.)

Przykład 2: nie nakładające się słowniki odwzorowujące 252 krótkie ciągi na liczby całkowite i na odwrót:

% 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 bardzo duża wygrana w mojej książce!

Po porównaniu tych dwóch zastanawiałem się, czy z1niska wydajność może być przypisana do kosztów związanych z tworzeniem dwóch list przedmiotów, co z kolei doprowadziło mnie do zastanowienia się, czy ta odmiana może 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 tego z3 jest nieco szybszy niż z1, ale nie tak szybko, jak z2. Zdecydowanie nie warto wszystkie dodatkowe pisanie.

W tej dyskusji wciąż brakuje czegoś ważnego, co jest porównaniem wydajności tych alternatyw z "oczywistym" sposobem łączenia dwóch list: za pomocą update metoda. Aby spróbować utrzymać równowagę rzeczy z wyrażeniami, z których żadna nie modyfikuje x lub y, zrobię kopię x zamiast modyfikować ją w miejscu, jak następuje:

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, z0 i z2 wydają się mieć zasadniczo identyczną wydajność. Myślisz, że to może być zbieg okoliczności? Ja nie....

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

Możesz również napisać to jako

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

tak jak Tony, ale (nie dziwi) różnica w zapisie okazuje się nie mieć żadnego mierzalnego wpływu na wydajność. Użyj tego, który wygląda dobrze dla Ciebie. Oczywiście, jest absolutnie poprawny, aby wskazać, że wersja z dwoma oświadczeniami jest znacznie łatwiejsza do zrozumienia.


116
2017-10-23 02:38



To nie działa w Pythonie 3; items() nie jest zdolny do catenable, i iteritems nie istnieje. - Antti Haapala


Chciałem czegoś podobnego, ale z możliwością określenia, jak wartości na duplikatach kluczy zostały scalone, więc zhakowałem to (ale nie testowałem go zbyt mocno). Oczywiście nie jest to pojedyncze wyrażenie, ale jest to wywołanie funkcji pojedynczej.

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

    Examples:

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

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

86
2017-09-04 19:08





W Pythonie 3 możesz użyć collections.ChainMap grupujące wiele dyktów lub innych mapowań w celu utworzenia pojedynczego, możliwego do aktualizacji widoku:

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

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

73
2018-04-28 03:15



Ale podczas korzystania z ChainMap należy zachować ostrożność, ponieważ jeśli masz zduplikowane klucze, używane są wartości z pierwszego mapowania i kiedy dzwonisz do del powiedzmy, że ChainMap c usunie pierwsze mapowanie tego klucza. - Prerit
@Prerit Czego innego byś się spodziewał? To jest normalny sposób działania sprzężonych przestrzeni nazw. Zastanów się, jak działa $ PATH w bashu. Usunięcie pliku wykonywalnego na ścieżce nie wyklucza dalszego działania pliku wykonywalnego o tej samej nazwie. - Raymond Hettinger
@Raymond Hettinger Zgadzam się, dodałem ostrożność. Większość ludzi może o tym nie wiedzieć. :RE - Prerit
Przybyłem tutaj, aby polecić prostotę collections.ChainMap. - hughdbrown
Właściwie ten rodzaj wyjścia, jaki tu masz, można osiągnąć nawet bez użycia ChainMap dobrze? To znaczy, wszystko co musisz zrobić, to po prostu zaktualizować z = {**x, **y} i postępuj zgodnie z tradycyjnymi krokami, to wszystko! - Steffi Keran Rani J