Python dict: получить против setdefault
Следующие два выражения кажутся мне эквивалентными. Какой из них предпочтительнее?
data = [('a', 1), ('b', 1), ('b', 2)]
d1 = {}
d2 = {}
for key, val in data:
# variant 1)
d1[key] = d1.get(key, []) + [val]
# variant 2)
d2.setdefault(key, []).append(val)
Результаты одинаковы, но какая версия лучше или скорее более питонна?
Лично я нахожу версию 2 труднее понять, так как для меня очень сложно понять setdefault. Если я правильно понимаю, он ищет значение "ключ" в словаре, если он недоступен, вводит "[]" в dict, возвращает ссылку на значение или "[]" и добавляет к нему "val" ссылка. Хотя, конечно, гладко, это не интуитивно, по крайней мере (по крайней мере для меня).
На мой взгляд, версию 1 легче понять (если доступно, получить значение для "ключа", если нет, получить "[]", затем объединить со списком, составленным из [val] и поместить результат в "ключ"). Но, хотя это более интуитивно понятно, я боюсь, что эта версия менее производительна, когда создается весь этот список. Другим недостатком является то, что "d1" встречается дважды в выражении, которое довольно подвержено ошибкам. Возможно, есть лучшая реализация с использованием get, но в настоящее время она ускользает от меня.
Я предполагаю, что версия 2, хотя ее труднее понять неопытным, быстрее и поэтому предпочтительнее. Мнения?
7 ответов
Ваши два примера делают то же самое, но это не значит, get
а также setdefault
делать.
Разница между ними в основном заключается в ручной настройке d[key]
указывать на список каждый раз, по сравнению с setdefault
автоматическая настройка d[key]
в список, только когда он не установлен.
Сделав два метода максимально похожими, я побежал
from timeit import timeit
print timeit("c = d.get(0, []); c.extend([1]); d[0] = c", "d = {1: []}", number = 1000000)
print timeit("c = d.get(1, []); c.extend([1]); d[0] = c", "d = {1: []}", number = 1000000)
print timeit("d.setdefault(0, []).extend([1])", "d = {1: []}", number = 1000000)
print timeit("d.setdefault(1, []).extend([1])", "d = {1: []}", number = 1000000)
и получил
0.794723378711
0.811882272256
0.724429205999
0.722129751973
Так setdefault
примерно на 10% быстрее, чем get
для этого.
get
метод позволяет делать меньше, чем вы можете с setdefault
, Вы можете использовать его, чтобы избежать KeyError
когда ключ не существует (если это часто случается), даже если вы не хотите устанавливать ключ.
См. Примеры использования для метода dict 'setdefault', а метод dict.get() возвращает указатель для получения дополнительной информации о двух методах.
Нить о setdefault
приходит к выводу, что большую часть времени вы хотите использовать defaultdict
, Нить о get
приходит к выводу, что это медленно, и часто вам лучше (по скорости) делать двойной поиск, использовать defaultdict или обрабатывать ошибку (в зависимости от размера словаря и вашего варианта использования).
Принятый ответ от agf не похож на лайк. После:
print timeit("d[0] = d.get(0, []) + [1]", "d = {1: []}", number = 10000)
d[0]
содержит список из 10000 элементов, тогда как после:
print timeit("d.setdefault(0, []) + [1]", "d = {1: []}", number = 10000)
d[0]
это просто []
, то есть d.setdefault
версия никогда не изменяет список, сохраненный в d
, Код на самом деле должен быть:
print timeit("d.setdefault(0, []).append(1)", "d = {1: []}", number = 10000)
и на самом деле быстрее, чем неисправный setdefault
пример.
Разница здесь на самом деле в том, что когда вы добавляете с помощью конкатенации, весь список копируется каждый раз (и как только у вас есть 10000 элементов, которые начинают измериться. Использование append
обновления списка амортизируются O(1), то есть фактически постоянным временем.
Наконец, есть два других варианта, которые не рассматриваются в первоначальном вопросе: defaultdict
или просто проверить словарь, чтобы увидеть, содержит ли он уже ключ.
Итак, предполагая d3, d4 = defaultdict(list), {}
# variant 1 (0.39)
d1[key] = d1.get(key, []) + [val]
# variant 2 (0.003)
d2.setdefault(key, []).append(val)
# variant 3 (0.0017)
d3[key].append(val)
# variant 4 (0.002)
if key in d4:
d4[key].append(val)
else:
d4[key] = [val]
вариант 1, безусловно, самый медленный, потому что он копирует список каждый раз, вариант 2 - второй самый медленный, вариант 3 - самый быстрый, но не будет работать, если вам нужен Python старше 2.5, а вариант 4 лишь немного медленнее, чем вариант 3,
Я бы сказал, использовать вариант 3, если вы можете, с вариантом 4 в качестве варианта для тех случайных мест, где defaultdict
не совсем подходит. Избегайте обоих ваших оригинальных вариантов.
Для тех, кто все еще не понимает эти два термина, позвольте мне рассказать вам о принципиальной разнице между методами get() и setdefault() -
Сценарий-1
root = {}
root.setdefault('A', [])
print(root)
Сценарий-2
root = {}
root.get('A', [])
print(root)
В Сценарии-1 выходной будет {'A': []}
в то время как в сценарии-2 {}
Так setdefault()
устанавливает отсутствующие ключи в поле get()
предоставляет только значение по умолчанию, но не изменяет словарь.
Теперь давайте перейдем туда, где это будет полезно. Предположим, вы ищете элемент в dict, значением которого является список, и вы хотите изменить этот список, если он найден, в противном случае создайте новый ключ с этим списком.
с помощью setdefault()
def fn1(dic, key, lst):
dic.setdefault(key, []).extend(lst)
с помощью get()
def fn2(dic, key, lst):
dic[key] = dic.get(key, []) + (lst) #Explicit assigning happening here
Теперь давайте рассмотрим время -
dic = {}
%%timeit -n 10000 -r 4
fn1(dic, 'A', [1,2,3])
Взял 288 нс
dic = {}
%%timeit -n 10000 -r 4
fn2(dic, 'A', [1,2,3])
Взял 128 с
Таким образом, существует очень большая разница во времени между этими двумя подходами.
Вы можете посмотреть на defaultdict
в collections
модуль. Следующее эквивалентно вашим примерам.
from collections import defaultdict
data = [('a', 1), ('b', 1), ('b', 2)]
d = defaultdict(list)
for k, v in data:
d[k].append(v)
Здесь больше
1. Объяснено с хорошим примером здесь:
http://code.activestate.com/recipes/66516-add-an-entry-to-a-dictionary-unless-the-entry-is-a/
ДИКТ. setdefault типичное использование
somedict.setdefault(somekey,[]).append(somevalue)
ДИКТ. получить типичное использование
theIndex[word] = 1 + theIndex.get(word,0)
2. Дополнительные пояснения: http://python.net/~goodger/projects/pycon/2007/idiomatic/handout.html
dict.setdefault()
эквивалентно get
или же set & get
, Или же set if necessary then get
, Это особенно эффективно, если ваш словарный ключ является дорогим для вычисления или длинным для ввода.
Единственная проблема с dict.setdefault() состоит в том, что значение по умолчанию всегда оценивается, независимо от того, нужно оно или нет. Это имеет значение только в том случае, если значение по умолчанию является дорогим для вычисления. В этом случае используйте defaultdict.
3. Наконец, официальные документы с разницей выделены http://docs.python.org/2/library/stdtypes.html
get(key[, default])
Возвращает значение для ключа, если ключ находится в словаре, иначе по умолчанию. Если default не задано, по умолчанию None, поэтому этот метод никогда не вызывает KeyError.
setdefault(key[, default])
Если ключ находится в словаре, вернуть его значение. Если нет, вставьте ключ со значением по умолчанию и верните значение по умолчанию. по умолчанию по умолчанию нет.
Логика dict.get
является:
if key in a_dict:
value = a_dict[key]
else:
value = default_value
Возьмите пример:
In [72]: a_dict = {'mapping':['dict', 'OrderedDict'], 'array':['list', 'tuple']}
In [73]: a_dict.get('string', ['str', 'bytes'])
Out[73]: ['str', 'bytes']
In [74]: a_dict.get('array', ['str', 'byets'])
Out[74]: ['list', 'tuple']
Механизм setdefault
является:
levels = ['master', 'manager', 'salesman', 'accountant', 'assistant']
#group them by the leading letter
group_by_leading_letter = {}
# the logic expressed by obvious if condition
for level in levels:
leading_letter = level[0]
if leading_letter not in group_by_leading_letter:
group_by_leading_letter[leading_letter] = [level]
else:
group_by_leading_letter[leading_letter].append(word)
In [80]: group_by_leading_letter
Out[80]: {'a': ['accountant', 'assistant'], 'm': ['master', 'manager'], 's': ['salesman']}
Метод setdefault dict предназначен именно для этой цели. Предыдущий цикл for можно переписать так:
In [87]: for level in levels:
...: leading = level[0]
...: group_by_leading_letter.setdefault(leading,[]).append(level)
Out[80]: {'a': ['accountant', 'assistant'], 'm': ['master', 'manager'], 's': ['salesman']}
Это очень просто, означает, что либо ненулевой список добавляет элемент, либо нулевой список добавляет элемент.
defaultdict
, что делает это еще проще. Чтобы создать его, вы передаете тип или функцию для генерации значения по умолчанию для каждого слота в dict:
from collections import defualtdict
group_by_leading_letter = defaultdict(list)
for level in levels:
group_by_leading_letter[level[0]].append(level)
На этот вопрос нет однозначного ответа. Оба они преследуют одну и ту же цель. Оба они могут использоваться для работы с отсутствующими значениями ключей. Единственное различие, которое я обнаружил, заключается в том, что с setdefault () ключ, который вы вызываете (если он не был ранее в словаре), автоматически вставляется, в то время как с get () этого не происходит. Вот пример:Setdefault()
>>> myDict = {'A': 'GOD', 'B':'Is', 'C':'GOOD'} #(1)
>>> myDict.setdefault('C') #(2)
'GOOD'
>>> myDict.setdefault('C','GREAT') #(3)
'GOOD'
>>> myDict.setdefault('D','AWESOME') #(4)
'AWESOME'
>>> myDict #(5)
{'A': 'GOD', 'B': 'Is', 'C': 'GOOD', 'D': 'AWSOME'}
>>> myDict.setdefault('E')
>>>
Получить()
>>> myDict = {'a': 1, 'b': 2, 'c': 3} #(1)
>>> myDict.get('a',0) #(2)
1
>>> myDict.get('d',0) #(3)
0
>>> myDict #(4)
{'a': 1, 'b': 2, 'c': 3}
Вот мой вывод: нет конкретного ответа, какой из них лучше всего, когда речь идет о вменении значений по умолчанию. Единственное отличие состоит в том, что setdefault () автоматически добавляет в словарь любой новый ключ со значением по умолчанию, а get () - нет. Для получения дополнительной информации перейдите сюда!
In [1]: person_dict = {}
In [2]: person_dict['liqi'] = 'LiQi'
In [3]: person_dict.setdefault('liqi', 'Liqi')
Out[3]: 'LiQi'
In [4]: person_dict.setdefault('Kim', 'kim')
Out[4]: 'kim'
In [5]: person_dict
Out[5]: {'Kim': 'kim', 'liqi': 'LiQi'}
In [8]: person_dict.get('Dim', '')
Out[8]: ''
In [5]: person_dict
Out[5]: {'Kim': 'kim', 'liqi': 'LiQi'}