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


213

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

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

Ответы:


311

Как это:

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

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"

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еще не задан, в отличие от того, что задан вопрос.


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"


Я проголосовал за вас, но проблема с вашим решением в том, что дикты работают с [], а лямбды работают с ()
— Юкашима Хуксай
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.