Словари и значения по умолчанию

213

Если предположить, connectionDetailsчто это словарь Python, какой самый лучший, самый элегантный, самый «питонный» способ рефакторинга кода, подобный этому?

if "host" in connectionDetails:
    host = connectionDetails["host"]
else:
    host = someDefaultValue
mnowotka
источник

Ответы:

311

Как это:

host = connectionDetails.get('host', someDefaultValue)
Matth
источник
40
Обратите внимание, что второй аргумент является значением, а не ключом.
Марцин
7
+1 для удобства чтения, но if/elseнамного быстрее. Это может или не может играть роль.
Тим Пицкер
7
@ Тим, Можете ли вы дать ссылку, почему if/elseбыстрее?
nishantjr
2
@Tim: Я предполагал, что одним из преимуществ использования языка более высокого уровня является то, что интерпретатор сможет «видеть» внутри функций и оптимизировать его - что пользователю не придется так много заниматься микрооптимизацией , Разве не для этого нужны компиляции JIT?
nishantjr
3
@nishantjr: Python (по крайней мере, CPython, наиболее распространенный вариант) не имеет JIT-компиляции. PyPy действительно может решить эту проблему быстрее, но я не установил ее, поскольку стандартный Python всегда был достаточно быстрым для моих целей. В целом, это вряд ли имеет значение в реальной жизни - если вам нужно делать критические вычисления по времени, Python, вероятно, не является языком выбора ...
Тим Питцкер,
99

Вы также можете использовать defaultdictтак:

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

Вы можете передать любую обычную функцию вместо лямбды:

from collections import defaultdict
def a():
  return 4

b = defaultdict(a, key="some_value")
b['absent'] => 4
b['key'] => "some_value"
tamerlaha
источник
7
Я пришел сюда по какой-то другой проблеме, нежели вопрос ОП, и ваше решение точно решает ее.
0xc0de
Я хотел бы +1, но, к сожалению, это не вписывается getни в подобные методы.
0xc0de
Этот ответ был мне полезен для обеспечения добавления в словарь включенных ключей по умолчанию. Моя реализация слишком длинна, чтобы описать ее в ответе StackOverflow, поэтому я написал об этом здесь. persagen.com/2020/03/05/…
Виктория Стюарт
24

Хотя .get()это хорошая идиома, она медленнее, чем if/else(и медленнее, чем try/exceptесли бы большую часть времени можно было ожидать присутствия ключа в словаре):

>>> 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
Я до сих пор не понимаю, почему if/then будет быстрее. В обоих случаях требуется поиск по словарю, и если вызов get()не так много медленнее, чем остальные счета для замедления?
Йенс
1
@Jens: вызовы функций стоят дорого.
Тим Пицкер
1
Что не должно иметь большого значения в густонаселенном словаре, правильно? Значение вызова функции не будет иметь большого значения, если фактический поиск дорог. Это, вероятно, имеет значение только в игрушечных примерах.
AturSams
2
@zehelvion: поиск O(1)в словаре не зависит от размера словаря, поэтому издержки вызова функции актуальны.
Тим Пицкер
35
это странно, если накладные расходы на вызов функции заставят вас отказаться от использования get. Используйте то, что ваши коллеги по команде могут читать лучше всего.
Йохен Бедерсдорфер
19

Для нескольких различных значений по умолчанию попробуйте это:

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
Это хорошее идиоматическое решение, но есть подводный камень. Непредвиденные результаты могут возникнуть, если для connectionDetails Noneуказано значение empty или строка в качестве одного из значений в парах ключ-значение. В defaultsсловаре может быть непреднамеренно пропущено одно из его значений. (см. также stackoverflow.com/questions/6354436 )
dreftymac
9

В словарях python есть метод для этого: dict.setdefault

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

Однако этот метод устанавливает значение connectionDetails['host']для someDefaultValueесли ключ hostеще не задан, в отличие от того, что задан вопрос.

Sriram
источник
1
Обратите внимание , что setdefault()возвращает значение, так что это работает , а также: host = connectionDetails.setdefault('host', someDefaultValue). Просто помните, что он установит connectionDetails['host']значение по умолчанию, если ключ не был там раньше.
ash108 16.10.16
7

(это поздний ответ)

Альтернативой является создание подкласса dictкласса и реализация __missing__()метода следующим образом:

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

Примеры:

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

Проверяя подозрение @Tim Pietzcker'а о ситуации в PyPy (5.2.0-alpha0) для Python 3.3.5, я обнаружил, что действительно оба .get()и if/ elseway работают одинаково. На самом деле кажется, что в случае if / else есть даже только один поиск, если условие и присваивание включают один и тот же ключ (сравните с последним случаем, когда есть два поиска).

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

Вы можете использовать функцию lamba для этого в качестве однострочника. Создайте новый объект, connectionDetails2доступ к которому осуществляется как функция ...

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

Сейчас использую

connectionDetails2(k)

вместо того

connectionDetails[k]

который возвращает значение словаря, если kнаходится в ключах, в противном случае он возвращает"DEFAULT"

Бобак Хашеми
источник
Я проголосовал за вас, но проблема с вашим решением в том, что дикты работают с [], а лямбды работают с ()
Юкашима Хуксай