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

Odpowiedzi:

311

Lubię to:

host = connectionDetails.get('host', someDefaultValue)
MattH
źródło
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"
tamerlaha
źródło
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
Tim Pietzcker
źródło
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
Jerome Baum
źródło
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.

Sriram
źródło
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'
Laurent LAPORTE
źródło
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
Do
źródło
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"

Bobak Hashemi
źródło
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