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