Słowniki i wartości domyślne


213

Zakładając, że connectionDetailsjest słownikiem Python, jaki jest najlepszy, najbardziej elegancki, najbardziej „pythoniczny” sposób refaktoryzacji kodu w ten sposób?

if "host" in connectionDetails:
    host = connectionDetails["host"]
else:
    host = someDefaultValue

Odpowiedzi:


311

Lubię to:

host = connectionDetails.get('host', someDefaultValue)

40
Zauważ, że drugi argument jest wartością, a nie kluczem.
Marcin

7
+1 za czytelność, ale if/elsejest znacznie szybszy. To może, ale nie musi, odgrywać pewną rolę.
Tim Pietzcker,

7
@Tim, czy możesz podać odniesienie do tego, dlaczego if/elsejest szybszy?
nishantjr

2
@Tim: Przyjąłem, że jedną z zalet używania języka wyższego poziomu jest to, że interpreter będzie w stanie „zobaczyć” funkcje i zoptymalizować go - że użytkownik nie będzie musiał radzić sobie z mikrooptymalizacjami . Czy nie po to są kompilacje JIT?
nishantjr

3
@nishantjr: Python (przynajmniej CPython, najczęstszy wariant) nie ma kompilacji JIT. PyPy może rzeczywiście rozwiązać to szybciej, ale nie mam go zainstalowanego, ponieważ standardowy Python zawsze był wystarczająco szybki do moich celów. Ogólnie rzecz biorąc, raczej nie ma to znaczenia w prawdziwym życiu - jeśli potrzebujesz krytycznego
czasowo skracania

99

Możesz także użyć defaultdictpodobnego:

from collections import defaultdict
a = defaultdict(lambda: "default", key="some_value")
a["blabla"] => "default"
a["key"] => "some_value"

Możesz przekazać dowolną zwykłą funkcję zamiast lambda:

from collections import defaultdict
def a():
  return 4

b = defaultdict(a, key="some_value")
b['absent'] => 4
b['key'] => "some_value"

7
Przybyłem tutaj z powodu innego problemu niż pytanie OP, a twoje rozwiązanie dokładnie to rozwiązuje.
0xc0de,

Dałbym +1, ale niestety nie pasuje do getani podobnych metod.
0xc0de,

Ta odpowiedź była dla mnie przydatna, ponieważ dodawałem do słownika klucze domyślne. Moja implementacja jest trochę za długa, aby opisać ją w odpowiedzi StackOverflow, więc napisałem o tym tutaj. persagen.com/2020/03/05/…
Victoria Stuart

24

Chociaż .get()jest ładnym idiomem, jest wolniejszy niż if/else(i wolniejszy niż, try/exceptjeśli przez większość czasu można oczekiwać obecności klucza w słowniku):

>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", 
... stmt="try:\n a=d[1]\nexcept KeyError:\n a=10")
0.07691968797894333
>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", 
... stmt="try:\n a=d[2]\nexcept KeyError:\n a=10")
0.4583777282275605
>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", 
... stmt="a=d.get(1, 10)")
0.17784020746671558
>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", 
... stmt="a=d.get(2, 10)")
0.17952161730158878
>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", 
... stmt="if 1 in d:\n a=d[1]\nelse:\n a=10")
0.10071221458065338
>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", 
... stmt="if 2 in d:\n a=d[2]\nelse:\n a=10")
0.06966537335119938

3
Nadal nie rozumiem, dlaczego if/then byłoby szybciej. Oba przypadki wymagają przeszukiwania słownika, a jeśli wywoływanie get()jest o wiele wolniejsze, co jeszcze tłumaczy spowolnienie?
Jens

1
@Jens: Wywołania funkcji są drogie.
Tim Pietzcker,

1
Co nie powinno być wielkim problemem w gęsto zaludnionym słowniku, prawda? Oznacza to, że wywołanie funkcji nie będzie miało większego znaczenia, jeśli faktyczne wyszukiwanie jest kosztowne. Prawdopodobnie ma to znaczenie tylko w przykładach zabawek.
AturSams

2
@zehelvion: Wyszukiwanie słownika jest O(1)niezależne od wielkości słownika, więc narzut związany z wywołaniem funkcji jest istotny.
Tim Pietzcker

35
dziwne jest, że narzut wywołany funkcją sprawi, że nie zdecydujesz się na użycie get. Użyj tego, co inni członkowie zespołu potrafią najlepiej przeczytać.
Jochen Bedersdorfer

19

W przypadku wielu różnych ustawień domyślnych spróbuj tego:

connectionDetails = { "host": "www.example.com" }
defaults = { "host": "127.0.0.1", "port": 8080 }

completeDetails = {}
completeDetails.update(defaults)
completeDetails.update(connectionDetails)
completeDetails["host"]  # ==> "www.example.com"
completeDetails["port"]  # ==> 8080

3
Jest to dobre rozwiązanie idiomatyczne, ale istnieje pułapka. Nieoczekiwane wyniki mogą wystąpić, jeśli zostanie dostarczony Noneparametr connectionDetails lub parametr emptyString jako jedna z wartości w parach klucz-wartość. W defaultssłowniku potencjalnie jedna z jego wartości może zostać przypadkowo wygaszona. (patrz także stackoverflow.com/questions/6354436 )
dreftymac

9

W słownikach Pythona istnieje taka metoda: dict.setdefault

connectionDetails.setdefault('host',someDefaultValue)
host = connectionDetails['host']

Jednak metoda ta ustawia wartość connectionDetails['host'], aby someDefaultValuejeśli klucz hostnie jest już zdefiniowane, w przeciwieństwie do tego, co zadał pytanie.


1
Zauważ, że setdefault()wartość zwrotów, tak to działa, jak również: host = connectionDetails.setdefault('host', someDefaultValue). Tylko uważaj, że ustawi connectionDetails['host']wartość domyślną, jeśli klucz nie był wcześniej.
ash108

7

(to późna odpowiedź)

Alternatywą jest podklasę dictklasy i implementację __missing__()metody w następujący sposób:

class ConnectionDetails(dict):
    def __missing__(self, key):
        if key == 'host':
            return "localhost"
        raise KeyError(key)

Przykłady:

>>> connection_details = ConnectionDetails(port=80)

>>> connection_details['host']
'localhost'

>>> connection_details['port']
80

>>> connection_details['password']
Traceback (most recent call last):
  File "python", line 1, in <module>
  File "python", line 6, in __missing__
KeyError: 'password'

4

Testując podejrzenia @ Tima Pietzckera dotyczące sytuacji w PyPy (5.2.0-alpha0) dla Pythona 3.3.5, stwierdzam, że rzeczywiście oba sposoby .get()i if/ i elsedziałają podobnie. Właściwie wydaje się, że w przypadku if / else jest nawet tylko jedno wyszukiwanie, jeśli warunek i przypisanie dotyczą tego samego klucza (porównaj z ostatnim przypadkiem, w którym są dwa wyszukiwania).

>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="try:\n a=d[1]\nexcept KeyError:\n a=10")
0.011889292989508249
>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="try:\n a=d[2]\nexcept KeyError:\n a=10")
0.07310474599944428
>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="a=d.get(1, 10)")
0.010391917996457778
>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="a=d.get(2, 10)")
0.009348208011942916
>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="if 1 in d:\n a=d[1]\nelse:\n a=10")
0.011475925013655797
>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="if 2 in d:\n a=d[2]\nelse:\n a=10")
0.009605801998986863
>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="if 2 in d:\n a=d[2]\nelse:\n a=d[1]")
0.017342638995614834

1

Możesz użyć do tego funkcji Lamba jako jednej linijki. Utwórz nowy obiekt, do connectionDetails2którego dostęp jest uzyskiwany jak funkcja ...

connectionDetails2 = lambda k: connectionDetails[k] if k in connectionDetails.keys() else "DEFAULT"

Teraz użyj

connectionDetails2(k)

zamiast

connectionDetails[k]

która zwraca wartość słownika, jeśli kjest w kluczach, w przeciwnym razie zwraca"DEFAULT"


Głosowałem za tobą, ale problem z twoim rozwiązaniem polega na tym, że dykta działa z [], ale lambda działa z ()
yukashima huksay
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.