Почему dict.get(ключ) вместо dict[ключ]?
Сегодня я наткнулся на dict
метод get
который, учитывая ключ в словаре, возвращает соответствующее значение.
Для чего эта функция полезна? Если я хочу найти значение, связанное с ключом в словаре, я могу просто сделать dict[key]
и возвращает то же самое:
dictionary = {"Name": "Harry", "Age": 17}
dictionary["Name"]
dictionary.get("Name")
19 ответов
Это позволяет вам указать значение по умолчанию, если ключ отсутствует:
dictionary.get("bogus", default_value)
возвращается default_value
(что бы вы ни выбрали), тогда как
dictionary["bogus"]
поднимет KeyError
,
Если опущено, default_value
является None
такой, что
dictionary.get("bogus") # <-- No default specified -- defaults to None
возвращается None
как
dictionary.get("bogus", None)
было бы.
Что
dict.get()
метод?
Как уже упоминалосьget
Метод содержит дополнительный параметр, который указывает на отсутствующее значение. Из документации
get(key[, default])
Возвращает значение для ключа, если ключ находится в словаре, иначе по умолчанию. Если default не задано, по умолчанию используется None, поэтому этот метод никогда не вызывает
KeyError
,
Примером может быть
>>> d = {1:2,2:3}
>>> d[1]
2
>>> d.get(1)
2
>>> d.get(3)
>>> repr(d.get(3))
'None'
>>> d.get(3,1)
1
Есть ли улучшения скорости где-нибудь?
Как уже упоминалось здесь,
Кажется, что все три подхода в настоящее время демонстрируют схожую производительность (в пределах примерно 10% друг от друга), более или менее независимую от свойств списка слов.
Ранееget
был значительно медленнее, однако теперь скорость почти сопоставима с дополнительным преимуществом возврата значения по умолчанию. Но чтобы очистить все наши запросы, мы можем протестировать довольно большой список (обратите внимание, что тест включает поиск только всех допустимых ключей)
def getway(d):
for i in range(100):
s = d.get(i)
def lookup(d):
for i in range(100):
s = d[i]
Теперь синхронизируем эти две функции, используяtimeit
>>> import timeit
>>> print(timeit.timeit("getway({i:i for i in range(100)})","from __main__ import getway"))
20.2124660015
>>> print(timeit.timeit("lookup({i:i for i in range(100)})","from __main__ import lookup"))
16.16223979
Как мы видим, поиск выполняется быстрее, чем поиск, так как поиск функции отсутствует. Это можно увидеть через dis
>>> def lookup(d,val):
... return d[val]
...
>>> def getway(d,val):
... return d.get(val)
...
>>> dis.dis(getway)
2 0 LOAD_FAST 0 (d)
3 LOAD_ATTR 0 (get)
6 LOAD_FAST 1 (val)
9 CALL_FUNCTION 1
12 RETURN_VALUE
>>> dis.dis(lookup)
2 0 LOAD_FAST 0 (d)
3 LOAD_FAST 1 (val)
6 BINARY_SUBSCR
7 RETURN_VALUE
Где это будет полезно?
Это будет полезно всякий раз, когда вы хотите указать значение по умолчанию при поиске в словаре. Это уменьшает
if key in dic:
val = key[dic]
else:
val = def_val
Для одной строки,val = dic.get(key,def_val)
Где это будет НЕ полезно?
Всякий раз, когда вы хотите вернутьKeyError
заявляя, что конкретный ключ недоступен. Возвращение значения по умолчанию также несет в себе риск того, что определенное значение по умолчанию также может быть ключевым!
Возможно ли иметь
get
как особенность вdict['key']
?
Да! Нам нужно реализовать__missing__
в подклассе dict.
Пример программы может быть
class MyDict(dict):
def __missing__(self, key):
return None
Небольшая демонстрация может быть
>>> my_d = MyDict({1:2,2:3})
>>> my_d[1]
2
>>> my_d[3]
>>> repr(my_d[3])
'None'
get
принимает второе необязательное значение. Если указанный ключ не существует в вашем словаре, то это значение будет возвращено.
dictionary = {"Name": "Harry", "Age": 17}
dictionary.get('Year', 'No available data')
>> 'No available data'
Если вы не дадите второй параметр, None
будет возвращен.
Если вы используете индексирование как в dictionary['Year']
несуществующие ключи подымут KeyError
,
О чем нужно помнить при использовании .get()
:
Если словарь содержит ключ, использованный при вызове .get()
и его ценность None
, то .get()
метод вернет None
даже если указано значение по умолчанию.
Например, следующие результаты: None
не 'alt_value'
как и следовало ожидать:
d = {'key': None}
d.get('key', 'alt_value')
.get()
второе значение возвращается только в том случае, если предоставленный ключ НЕ находится в словаре, а не если возвращаемое значение этого вызова None
.
Я приведу практический пример очистки веб-данных с помощью Python, во многих случаях вы получите ключи без значений, в этих случаях вы получите ошибки, если будете использовать словарь ['key'], тогда как dictionary.get('ключ ', 'return_otherwise') не имеет проблем.
Точно так же, я бы использовал ''.join(list), а не list[0], если вы попытаетесь захватить одно значение из списка.
Надеюсь, поможет.
[Править] Вот практический пример:
Скажем, вы вызываете API, который возвращает файл JOSN, который вам нужно проанализировать. Первый JSON выглядит следующим образом:
{"bids":{"id":16210506,"submitdate":"2011-10-16 15:53:25","submitdate_f":"10\/16\/2011 at 21:53 CEST","submitdate_f2":"p\u0159ed 2 lety","submitdate_ts":1318794805,"users_id":"2674360","project_id":"1250499"}}
Второй JOSN выглядит так:
{"bids":{"id":16210506,"submitdate":"2011-10-16 15:53:25","submitdate_f":"10\/16\/2011 at 21:53 CEST","submitdate_f2":"p\u0159ed 2 lety","users_id":"2674360","project_id":"1250499"}}
Обратите внимание, что во втором JSON отсутствует ключ "submitdate_ts", что вполне нормально для любой структуры данных.
Поэтому, когда вы пытаетесь получить доступ к значению этого ключа в цикле, вы можете вызвать его с помощью следующего:
for item in API_call:
submitdate_ts = item["bids"]["submitdate_ts"]
Вы могли бы, но это даст вам ошибку отслеживания для второй строки JSON, потому что ключ просто не существует.
Подходящим способом кодирования этого может быть следующее:
for item in API_call:
submitdate_ts = item.get("bids", {'x': None}).get("submitdate_ts")
{'x': Нет}, чтобы избежать получения ошибки на втором уровне. Конечно, вы можете повысить отказоустойчивость в коде, если выполняете очистку. Как сначала указать условие if
Цель состоит в том, чтобы вы могли дать значение по умолчанию, если ключ не найден, что очень полезно
dictionary.get("Name",'harry')
Для чего эта функция полезна?
Одно конкретное использование подсчета со словарем. Предположим, вы хотите посчитать количество вхождений каждого элемента в данном списке. Обычный способ сделать это - создать словарь, в котором ключи - это элементы, а значения - это число вхождений.
fruits = ['apple', 'banana', 'peach', 'apple', 'pear']
d = {}
for fruit in fruits:
if fruit not in d:
d[fruit] = 0
d[fruit] += 1
Используя метод.get(), вы можете сделать этот код более компактным и понятным:
for fruit in fruits:
d[fruit] = d.get(fruit, 0) + 1
В других ответах четко объяснена разница между ключом скобки dict и упоминается довольно безобидная ловушка , когда или значение по умолчанию также является допустимым ключом.
Учитывая эту информацию, может возникнуть заманчивый вывод, что это безопаснее и лучше, чем индексирование в квадратных скобках, и его всегда следует использовать вместо поиска в скобках, как утверждается в статье «Прекратите использовать нотацию с квадратными скобками для получения значения словаря в Python », даже в обычном случае, когда они ожидают, что поиск увенчается успехом (т.е. никогда не вызовет ).
Автор сообщения в блоге утверждает, что «защищает ваш код»:
Обратите внимание, как попытка сослаться на несуществующий термин приводит к
KeyError
. Это может вызвать серьезные проблемы, особенно при работе с непредсказуемыми бизнес-данными.Хотя мы могли бы обернуть наше выражение в / или
if
утверждение, такое большое внимание к словарному термину быстро накапливается.
Это правда, что в редком случае объединения null () или иного заполнения отсутствующего значения для обработки непредсказуемых динамических данных разумно развернутый является полезным и сокращенным инструментом Python для неуклюжих
if key in dct:
а также /
except
блоки, которые существуют только для установки значений по умолчанию, когда ключ может отсутствовать как часть поведенческой спецификации для программы.
Однако замена всех поисковых запросов в скобках, включая те, которые, как вы утверждаете, должны быть успешными, — это другое дело. Эта практика эффективно понижает класс ошибок времени выполнения , которые помогают выявлять ошибки, до скрытых сценариев недопустимого состояния, которые, как правило, сложнее идентифицировать и отлаживать.
Распространенная ошибка среди программистов состоит в том, что они думают, что исключения вызывают головную боль, и пытаются их подавить, используя такие методы, как упаковка кода в ...
except: pass
блоки . Позже они понимают, что настоящая головная боль заключается в том, чтобы никогда не увидеть нарушение логики приложения в момент сбоя и развертывание неработающего приложения. Лучшей практикой программирования является использование утверждений для всех инвариантов программы, таких как ключи, которые должны быть в словаре.
Иерархия безопасности ошибок в широком смысле такова:
Когда разработчики языков программирования говорят о безопасности программ, основная цель состоит в том, чтобы обнаружить, а не скрыть подлинные ошибки, превратив ошибки времени выполнения в ошибки времени компиляции и превратив скрытые логические ошибки либо в исключения времени выполнения, либо (в идеале) в ошибки времени компиляции.
Python, задуманный как интерпретируемый язык, в значительной степени зависит от исключений во время выполнения, а не от ошибок компилятора. Отсутствующие методы или свойства, недопустимые операции с типами, такие как
1 + "a"
и выход за пределы или отсутствующие индексы или ключи поднимаются по умолчанию.
Некоторые языки, такие как JS, Java, Rust и Go, используют резервное поведение для своих карт по умолчанию (и во многих случаях не предоставляют альтернативу throw/raise), но Python создает исключения по умолчанию, наряду с другими языками, такими как C#. Perl/PHP выдает предупреждение о неинициализированном значении.
Неразборчивое применение ко всем обращениям к dict, даже к тем, которые, как ожидается, не завершатся неудачно и не имеют запасного варианта для работы с (или любым другим используемым по умолчанию) выходом из-под контроля кода, в значительной степени отбрасывает систему безопасности Python во время выполнения для этого класса ошибок, замалчивая или добавляя косвенность к потенциальным ошибкам.
Другие причины, по которым следует предпочесть поиск в квадратных скобках (со случайными, хорошо расположенными там, где ожидается значение по умолчанию):
- Предпочитаете писать стандартный, идиоматический код, используя инструменты, предоставляемые языком. Программисты Python обычно (правильно) предпочитают квадратные скобки из соображений безопасности исключений, указанных выше, и потому, что это поведение по умолчанию для дикторов Python.
- Всегда используйте намерение неустойки, создавая случаи, когда вы ожидаете предоставить значение по умолчанию, неотличимое от поиска, который, как вы утверждаете, должен быть успешным.
- Тестирование усложняется пропорционально новым «легальным» путям программы, разрешенным . По сути, каждый поиск теперь представляет собой ветвь, которая может быть успешной или неудачной — оба случая должны быть проверены, чтобы установить покрытие, даже если путь по умолчанию фактически недоступен по спецификации (по иронии судьбы это приводит к дополнительным
if val is not None:
или жеtry
для всех будущих применений полученного значения; ненужным и запутанным для чего-то, что никогда не должно бытьNone
в первую очередь). - немного медленнее .
- труднее набирать и уродливее читать (сравните
ArrayList
синтаксис для нативного C#Lists
или векторный код C++). Незначительный.
Некоторые языки, такие как C++ и Ruby, предлагают альтернативные методы (
at
а также
fetch
, соответственно), чтобы согласиться на выдачу ошибки при неправильном доступе, в то время как C# предлагает резервное значение согласия.
TryGetValue
похоже на Python
get
.
Поскольку JS, Java, Ruby, Go и Rust по умолчанию внедряют резервный подход во все поиски хэшей, можно подумать, что это не так уж плохо. Это правда, что это не самая большая проблема, стоящая перед разработчиками языков, и существует множество вариантов использования версии без броска доступа, поэтому неудивительно, что между языками нет единого мнения.
Но, как я уже говорил, Python (наряду с C#) добился большего успеха, чем эти языки, сделав параметр assert параметром по умолчанию. Это потеря безопасности и выразительности, чтобы отказаться от использования его для сообщения о нарушениях контракта в момент сбоя путем неизбирательного использования
.get
пересечь границу.
Почему dict.get(ключ) вместо dict[ключ]?
0. Резюме
По сравнению с dict[key]
, dict.get
обеспечивает запасное значение при поиске ключа.
1. Определение
get (key [, default]) 4. Встроенные типы - документация по Python 3.6.4rc1
Возвращает значение для ключа, если ключ находится в словаре, иначе по умолчанию. Если default не задано, по умолчанию None, поэтому этот метод никогда не вызывает KeyError.
d = {"Name": "Harry", "Age": 17}
In [4]: d['gender']
KeyError: 'gender'
In [5]: d.get('gender', 'Not specified, please add it')
Out[5]: 'Not specified, please add it'
2. Проблема, которую он решает.
Если без default value
Вы должны написать громоздкие коды для обработки такого исключения.
def get_harry_info(key):
try:
return "{}".format(d[key])
except KeyError:
return 'Not specified, please add it'
In [9]: get_harry_info('Name')
Out[9]: 'Harry'
In [10]: get_harry_info('Gender')
Out[10]: 'Not specified, please add it'
Как удобное решение, dict.get
вводит необязательное значение по умолчанию, позволяющее избежать вышеуказанных кодов.
3. Вывод
dict.get
имеет дополнительную опцию значения по умолчанию для обработки исключения, если ключ отсутствует в словаре
Одно отличие, которое может быть преимуществом, заключается в том, что если мы ищем ключ, которого не существует, мы получим None, в отличие от того, когда мы используем нотацию скобок, и в этом случае мы получим ошибку:
print(dictionary.get("address")) # None
print(dictionary["address"]) # throws KeyError: 'address'
Последнее, что здорово в методе get, это то, что он получает дополнительный необязательный аргумент для значения по умолчанию, то есть если мы пытались получить значение оценки учащегося, но у учащегося нет ключа оценки, который мы можем получить 0 вместо этого.
Поэтому вместо того, чтобы делать это (или что-то подобное):
score = None
try:
score = dictionary["score"]
except KeyError:
score = 0
Мы можем это сделать:
score = dictionary.get("score", 0)
# score = 0
Еще один вариант использования, о котором я не упоминаю, - это
key
аргумент для таких функций, как
sorted
,
max
а также
min
. В
get
Метод позволяет возвращать ключи на основе их значений.
>>> ages = {"Harry": 17, "Lucy": 16, "Charlie": 18}
>>> print(sorted(ages, key=ages.get))
['Lucy', 'Harry', 'Charlie']
>>> print(max(ages, key=ages.get))
Charlie
>>> print(min(ages, key=ages.get))
Lucy
Благодаря этому ответу на другой вопрос за предоставление этого варианта использования!
Короткий ответ
Квадратные скобки используются для условного поиска, который может завершиться ошибкой сKeyError
когда ключ отсутствует.
Этот метод используется при безусловном поиске, который никогда не завершается ошибкой, поскольку было предоставлено значение по умолчанию.
Базовый метод и вспомогательный метод
Квадратные скобки называют__getitem__
метод, который является фундаментальным для отображений, таких как dicts.
The get()
метод является вспомогательным слоем поверх этой функциональности. Это сокращение для общего шаблона кодирования:
try:
v = d[k]
except KeyError:
v = default_value
Для каких целей полезна эта функция?
Еще один вариант использования, гдеget()
полезно, если оно вызывает встроенную функцию из словаря. Как уже упоминалось в других ответах, значение по умолчанию может быть указано дляdict.get
, что означает, что сам ключ может быть возвращен, если его нет в словаре, напримерmy_dict.get(key, key)
. Это означает, что мы можем очень кратко использовать замену значений.
Например, из словаряdct = {1: 10}
, мы можем создать функциюreplacer = dct.get
(type(mapper)
возвращаетbuiltin_function_or_method
). Затем эту функцию можно сопоставить для замены значений.
lst = [0, 1, 2, 3, 4]
new_list = list(map(replacer, lst, lst)) # [0, 10, 2, 3, 4]
На самом деле поиск значений с помощью функции, вызываемойdict.get()
. Следующий эксперимент показывает, что поиск с помощью функции происходит более чем в 2 раза быстрее, чем поиск с помощью словаря (это было сделано на Python 3.9.12).
import timeit
setup = "lst = [0,1]*10000; dct = {1: 10}; replacer = dct.get"
t1 = min(timeit.repeat("list(map(replacer, lst, lst))", setup, number=100))
t2 = min(timeit.repeat("[dct[k] if k in dct else k for k in lst]", setup, number=100))
print(t2 / t1) # 2.707056842200316
Кроме того, функцияdiction.get позволяет указать значение по умолчанию, если ключ не существует, а другой нет. Вы можете вернуть None, если ключ не существует. Синтаксис:
dictionary.get('key', default)
Это становится чрезвычайно удобным, если вы хотите обновить столбцы базы данных, если они заданы, в противном случае используйте существующее значение в одной строке.
instance.first_name = validated_data.get('first_name',instance.first_name)
instance.last_name = validated_data.get('last_name',instance.first_name)
instance.save()
dict.get
по умолчанию ничего не вернет, если ключ не существует, но если вы укажете второй аргумент, он вернет его, если ключ не существует.Ото
dict[key]
подниметKeyError
если ключ не существует
Вот пример (читай комментарии):
>>> d={'a':[1,2,3],'b':[4,5,6]} # Create a dictionary
>>> d['c'] # Hoops, error key does not exist
Traceback (most recent call last):
File "<pyshell#7>", line 1, in <module>
d['c']
KeyError: 'c'
>>> d.get('c') # no error because of `get`, so nothing returned
>>> print(d.get('c')) # i print it, oh `None` is the output
None
>>> d.get('c',100) # Okay now i set second argument's value to `100`, hoopa output is `100`
100
>>> d['a'] # Works, key exist
[1, 2, 3]
>>> d.get('a') # work too, key exist
[1, 2, 3]
В Python 3.8 и более поздних версиях метод словаря можно использовать с оператором walrus.:=
в выражении присваивания для дальнейшего сокращения кода:
if (name := dictonary.get("Name")) is not None
return name
С использованием[]
вместоget()
потребуется обернуть код в блок try/except и перехватитьKeyError
(не показано). А без оператора walrus вам понадобилась бы еще одна строка кода:
name = dictionary.get("Name")
if (name is not None)
return name
Это позволяет вам указать значение по умолчанию вместо получения ошибки, когда значение не найдено. persuedocode следующим образом:
class dictionary():
def get(self,key,default):
if self[key] is not found :
return default
else:
return self[key]
В зависимости от использования следует использовать это get
метод.
Example1
In [14]: user_dict = {'type': False}
In [15]: user_dict.get('type', '')
Out[15]: False
In [16]: user_dict.get('type') or ''
Out[16]: ''
Example2
In [17]: user_dict = {'type': "lead"}
In [18]: user_dict.get('type') or ''
Out[18]: 'lead'
In [19]: user_dict.get('type', '')
Out[19]: 'lead'
.get()
дает вам «неявное»try: ... except:
, что делает код чище и надежнее, когда вы к нему привыкнете.