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'}
Другие вопросы по тегам