Python dict: получить против setdefault - PullRequest
43 голосов
/ 15 сентября 2011

Следующие два выражения кажутся мне эквивалентными.Какой из них предпочтительнее?

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 ]

24 голосов
/ 15 сентября 2011

Ваши два примера делают то же самое, но это не значит, что 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 или обработать ошибку (в зависимости от размера словаря и вашего варианта использования).

14 голосов
/ 15 сентября 2011

Принятый ответ от 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.

Разница здесь на самом деле в том, что когда вы добавляете с помощью конкатенации, весь списоккопируется каждый раз (и когда у вас есть 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 не совсем подходит. Избегайте обоих ваших илиоригинальные варианты.

10 голосов
/ 15 сентября 2011

Возможно, вы захотите посмотреть на 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)

Здесь больше здесь .

3 голосов
/ 11 марта 2014

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

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

1 голос
/ 25 сентября 2018

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

Scenario-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 с

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

1 голос
/ 17 июня 2015
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'}
0 голосов
/ 18 апреля 2018

Логика 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)
...