Python dict: get vs 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, хотя ее сложнее понять для неопытных, быстрее и, следовательно, предпочтительнее. Мнения?
Ответы
Ответ 1
Ваши два примера делают то же самое, но это не означает get
и setdefault
do.
Разница между этими двумя параметрами в основном настраивается вручную 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
So setdefault
для этой цели примерно на 10% быстрее, чем get
.
Метод get
позволяет сделать меньше, чем вы можете с помощью setdefault
. Вы можете использовать его, чтобы избежать получения KeyError
, когда ключ не существует (если это происходит часто), даже если вы не хотите устанавливать ключ.
Смотрите Использовать случаи для метода setdefault dict и метод dict.get() возвращает указатель для получения дополнительной информации об этих двух методах.
В потоке setdefault
делается вывод, что большую часть времени вы хотите использовать defaultdict
. В потоке get
делается вывод, что он медленный, и часто вам лучше (скорость) делать двойной поиск, используя defaultdict или обрабатывать ошибку (в зависимости от размера словаря и вашего прецедента).
Ответ 2
Принятый ответ от agf не сравнивается, как с подобным. После того, как:
print timeit("d[0] = d.get(0, []) + [1]", "d = {1: []}", number = 10000)
d[0]
содержит список из 10 000 элементов, а после:
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
.
Разница здесь действительно в том, что когда вы добавляете с помощью конкатенации, весь список копируется каждый раз (и как только у вас есть 10 000 элементов, которые начинают становиться измеримыми. Используя 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
не подходит. Избегайте обоих исходных вариантов.
Ответ 3
Вы можете посмотреть 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)
Здесь больше здесь.
Ответ 4
1. Объяснение с хорошим примером здесь:
http://code.activestate.com/recipes/66516-add-an-entry-to-a-dictionary-unless-the-entry-is-a/
dict. setdefault типичное использование
somedict.setdefault(somekey,[]).append(somevalue)
dict. получить типичное использование
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])
Если ключ находится в словаре, верните его значение. Если нет, вставить ключ со значением по умолчанию и вернуть значение по умолчанию. по умолчанию по умолчанию - None.
Ответ 5
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'}