python dict: получить 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)

результат тот же, но какая версия лучше, точнее более подходящие для Python?

лично я нахожу версию 2 сложнее понять, так как для меня setdefault очень сложно понять. Если я правильно понимаю, он ищет значение " key "в словаре, если оно недоступно, вводит "[] "в дикт, возвращает ссылку либо на значение, либо на "[] "и добавляет" val " к эта ссылка. Хотя, конечно, гладко, это не интуитивно (по крайней мере, для меня).

на мой взгляд, версия 1 легче понять (если доступно, получите значение для "key", если нет, получите "[]", затем присоединитесь к списку, составленному из [val], и поместите результат в "key"). Но в то время как более интуитивно понять, я боюсь, что эта версия менее эффективна, со всем этим списком создания. Другим недостатком является то, что" d1 " встречается дважды в выражении, которое довольно подвержено ошибкам. Возможно существует лучшая реализация с использованием get, но в настоящее время она ускользает от меня.

Я предполагаю, что версия 2, хотя и более трудная для понимания для неопытных, быстрее и, следовательно, предпочтительнее. Мнения?

7 ответов


ваши два примера делают то же самое, но это не означает 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

так setdefault примерно на 10% быстрее, чем get для этого цель.

на get метод позволяет сделать меньше чем вы можете с setdefault. Вы можете использовать его, чтобы избежать KeyError когда ключ не существует (если это то, что происходит часто), даже если вы не хотите установить ключ.

посмотреть варианты использования для метода "setdefault" dict и дикт.метод get () возвращает указатель для получения дополнительной информации о двух методах.

нить о setdefault приходит к выводу, что большую часть времени, вы хотите использовать defaultdict. Нить о get приходит к выводу, что это медленно, и часто вам лучше (скорость) делать двойной поиск, используя defaultdict или обработку ошибки (в зависимости от размера словаря и вашего варианта использования).


принятый ответ от 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 не совсем подходит. Избегайте обоих вариантов.


вы можете посмотреть на 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. Объяснено хорошим примером here:
http://code.activestate.com/recipes/66516-add-an-entry-to-a-dictionary-unless-the-entry-is-a/

дикт.setdefault типичное использование
somedict.setdefault(somekey,[]).append(somevalue)

дикт. get типичное использование
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])
Возвращает значение для key, если key находится в словаре, иначе по умолчанию. Если значение по умолчанию не задано, по умолчанию оно равно None, так что этот метод никогда поднимает KeyError.

setdefault(key[, default])
Если ключ находится в словаре, верните его значение. Если нет,вставить ключ со значением по умолчанию и вернуть по умолчанию. по умолчанию по умолчанию нет.


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

логика 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']

в mechamism из 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']}

это очень просто, означает, что либо ненулевой список добавляет элемент или нулевой список добавляет элемент.

The defaultdict, что делает это еще проще. Чтобы создать его, вы передаете тип или функция для генерации значений по умолчанию для каждого слота в dict:

from collections import defualtdict
group_by_leading_letter = defaultdict(list)
for level in levels:
    group_by_leading_letter[level[0]].append(level)

для тех, кто все еще борется в понимании этих двух терминов, позвольте мне сказать вам основное различие между get() и setdefault() метод -

Сценарий-1

root = {}
root.setdefault('A', [])
print(root)

Сценарий-2

root = {}
root.get('A', [])
print(root)

в сценарии-1 выход будет {'A': []} в то время как в сценарии-2 {}

так setdefault() наборы отсутствуют ключи в dict а 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 s

так что есть очень большая разница между этими двумя подходами.