Використовуйте випадки для методу dict 'setdefault'


192

Додавання collections.defaultdictв Python 2.5 значно зменшило потребу dictв setdefaultметоді s . Це питання стосується нашої колективної освіти:

  1. Для чого все setdefaultще корисно сьогодні в Python 2.6 / 2.7?
  2. Які популярні випадки використання setdefaultбули замінені collections.defaultdict?

1
Трохи пов’язані також stackoverflow.com/questions/7423428/…
користувач

Відповіді:


208

Ви можете сказати, що defaultdictвін корисний для налаштувань за замовчуванням перед заповненням дікта та setdefaultкорисний для встановлення значень за замовчуванням під час або після заповнення дікта .

Мабуть, найпоширеніший випадок використання: Групування елементів (у несортованих даних, інше використання itertools.groupby)

# really verbose
new = {}
for (key, value) in data:
    if key in new:
        new[key].append( value )
    else:
        new[key] = [value]


# easy with setdefault
new = {}
for (key, value) in data:
    group = new.setdefault(key, []) # key might exist already
    group.append( value )


# even simpler with defaultdict 
from collections import defaultdict
new = defaultdict(list)
for (key, value) in data:
    new[key].append( value ) # all keys have a default already

Іноді потрібно переконатися, що конкретні ключі існують після створення диктату. defaultdictне працює в цьому випадку, оскільки він створює лише ключі при явному доступі. Подумайте, що ви використовуєте щось HTTP-ish з багатьма заголовками - деякі необов'язкові, але ви хочете, щоб вони були за замовчуванням:

headers = parse_headers( msg ) # parse the message, get a dict
# now add all the optional headers
for headername, defaultvalue in optional_headers:
    headers.setdefault( headername, defaultvalue )

1
Дійсно, цей ІМХО є основним випадком використання на заміну defaultdict. Чи можете ви навести приклад того, що ви маєте на увазі в першому абзаці?
Елі Бендерський

2
Мухаммад Алкароурі: Перше, що ви робите, - це скопіювати дік, а потім переписати деякі елементи. Я теж багато цього роблю, і я думаю, що насправді ідіома найбільше надає перевагу setdefault. А defaultdictз іншого боку, не буде працювати, якби не всі defaultvaluesрівні (тобто деякі є, 0а деякі є []).
Йохен Рітцель

2
@ YHC4k, так. Саме тому я і використовував headers = dict(optional_headers). Для випадку, коли значення за замовчуванням не всі рівні. І кінцевий результат такий самий, як якщо ви спочатку отримаєте заголовки HTTP, а потім встановіть параметри за замовчуванням для тих, яких ви не отримали. І це цілком корисно, якщо у вас вже є optional_headers. Спробуйте мій заданий 2-х крок код і порівняйте його з вашим, і ви побачите, що я маю на увазі.
Мухаммед Алкароурі

19
або просто робитиnew.setdefault(key, []).append(value)
fmalina

2
Мені здається дивним, що найкраща відповідь зводиться defaultdictнавіть до кращого, ніж setdefault(так де зараз випадок використання?). Крім того, ChainMapкраще розглядати httpприклад, IMO.
YvesgereY

29

Я зазвичай використовую setdefaultдля аргументів ключових слів аргументи, наприклад, у цій функції:

def notify(self, level, *pargs, **kwargs):
    kwargs.setdefault("persist", level >= DANGER)
    self.__defcon.set(level, **kwargs)
    try:
        kwargs.setdefault("name", self.client.player_entity().name)
    except pytibia.PlayerEntityNotFound:
        pass
    return _notify(level, *pargs, **kwargs)

Це чудово для налаштування аргументів у обгортках навколо функцій, які беруть аргументи ключових слів.


16

defaultdict чудово, коли значення за замовчуванням є статичним, як новий список, але не настільки, якщо воно динамічне.

Наприклад, мені потрібен словник для відображення рядків в унікальні вставки. defaultdict(int)завжди буде використовувати 0 для значення за замовчуванням. Так само defaultdict(intGen())завжди виробляє 1.

Натомість я використав звичайний диктант:

nextID = intGen()
myDict = {}
for lots of complicated stuff:
    #stuff that generates unpredictable, possibly already seen str
    strID = myDict.setdefault(myStr, nextID())

Зауважте, що dict.get(key, nextID())недостатньо, тому що мені потрібно мати можливість посилатися на ці значення також пізніше.

intGen - це крихітний клас, який я будую, який автоматично збільшує int і повертає його значення:

class intGen:
    def __init__(self):
        self.i = 0

    def __call__(self):
        self.i += 1
    return self.i

Якщо у когось є спосіб це зробити, defaultdictя хотів би це побачити.


для спосіб зробити це з (підклас) defaultdict см це питання: stackoverflow.com/questions/2912231 / ...
Weronika

8
Ви можете замінити intGenна itertools.count().next.
Сурма

7
nextID()значення буде збільшуватися щоразу, коли myDict.setdefault()викликається, навіть якщо значення, яке воно повертається, не використовується як strID. Це здається марнотратним і ілюструє одне з речей, які мені взагалі не подобаються, setdefault()а саме те, що він завжди оцінює свій defaultаргумент, чи він насправді звикає.
martineau

Ви можете зробити це з defaultdict: myDict = defaultdict(lambda: nextID()). Пізніше, strID = myDict[myStr]в петлі.
musiphil

3
Щоб отримати поведінку, яку ви описуєте за замовчуванням, чому б не просто myDict = defaultdict(nextID)?
сорок_двох

10

Я використовую, setdefault()коли хочу значення за замовчуванням у OrderedDict. Не існує стандартної колекції Python, яка робить і те, і інше, але є способи реалізувати таку колекцію.


10

Оскільки більшість відповідей указує setdefaultабо defaultdictдозволить вам встановити значення за замовчуванням, коли ключ не існує. Однак я хотів би зазначити невеликий застереження щодо випадків використання setdefault. Коли інтерпретатор Python виконує, setdefaultвін завжди буде оцінювати другий аргумент функції, навіть якщо ключ є в словнику. Наприклад:

In: d = {1:5, 2:6}

In: d
Out: {1: 5, 2: 6}

In: d.setdefault(2, 0)
Out: 6

In: d.setdefault(2, print('test'))
test
Out: 6

Як бачимо, printвін також був виконаний, хоча 2 вже існували у словнику. Це стає особливо важливим, якщо ви плануєте використовувати, setdefaultнаприклад, оптимізацію memoization. Якщо ви додасте рекурсивний виклик функції в якості другого аргументу setdefault, ви не отримаєте з нього ніякої продуктивності, оскільки Python завжди буде викликати функцію рекурсивно.

Оскільки згадування про запам'ятовування, кращою альтернативою є використання декоратора functools.lru_cache, якщо ви розглядаєте можливість вдосконалення функції за допомогою запам'ятовування. lru_cache краще обробляє вимоги кешування для рекурсивної функції.


8

Як сказав Мухаммед, бувають ситуації, в яких ви іноді бажаєте встановити значення за замовчуванням. Прекрасним прикладом цього є структура даних, яка спочатку заповнюється, а потім запитується.

Розглянемо трійку. Коли ви додаєте слово, якщо потрібний підменю, але його немає, його слід створити для розширення трійки. Під час запиту на наявність слова відсутній підрозділ вказує на те, що це слово немає і його не слід створювати.

Вирок за замовчуванням не може цього зробити. Натомість необхідно використовувати звичайний дикт із методами get та setdefault.


5

Теоретично кажучи, setdefaultвсе-таки було б зручно, якщо іноді ви хочете встановити за замовчуванням, а іноді ні. У реальному житті я ще не зустрічав такого випадку використання.

Однак цікавий випадок використання виходить із стандартної бібліотеки (Python 2.6, _threadinglocal.py):

>>> mydata = local()
>>> mydata.__dict__
{'number': 42}
>>> mydata.__dict__.setdefault('widgets', [])
[]
>>> mydata.widgets
[]

Я б сказав, що використання __dict__.setdefault- досить корисний випадок.

Редагувати : Як це буває, це єдиний приклад у стандартній бібліотеці, і це коментар. Тож, можливо, недостатньо справи, щоб виправдати існування setdefault. Все ж ось ось пояснення:

Об'єкти зберігають свої атрибути в __dict__атрибуті. Як це відбувається, __dict__атрибут може бути записаний у будь-який час після створення об’єкта. Це також словник не a defaultdict. Об'єкти в загальному випадку не є розумними __dict__як такі, defaultdictоскільки це зробило б, щоб кожен об'єкт мав усі юридичні ідентифікатори як атрибути. Тож я не можу передбачити жодних змін до позбавлення від об'єктів Python __dict__.setdefault, окрім видалення їх, якщо це було б не корисно.


1
Не могли б ви детальніше розібратися - що робить _dict .setdefault особливо корисним?
Елі Бендерський

1
@Eli: Я думаю, справа в тому, що __dict__це реалізація a dict, а не a defaultdict.
Катріель

1
Добре. Я не проти setdefaultзалишатися в Python, але цікаво бачити, що зараз це майже марно.
Елі Бендерський

@Eli: Я згоден. Я не думаю, що є достатньо причин для того, щоб його сьогодні запровадили, якщо його не було. Але перебуваючи там уже, важко буде заперечити його видалення, враховуючи весь код, який вже використовує його.
Мухаммед Алкароурі

1
Файл під оборонним програмуванням. setdefaultробить явним , що ви присвоювання Словника через ключ , який може або не може існувати, і якщо вона не існує , ви хочете , він створив зі значенням за замовчуванням, наприклад d.setdefault(key,[]).append(value). В іншому місці програми ви робите alist=d[k]там, де обчислюється k, і ви хочете, щоб виняток було викинуто, якщо k in not in d (що може мати знадобитися assert k in dабо навітьif not ( k in d): raise KeyError
nigel222

3

Одним з недоліків defaultdictover dict( dict.setdefault) є те, що defaultdictоб’єкт створює новий елемент. ВСІМКУ надається неіснуючий ключ (наприклад, з ==, print). Також defaultdictклас, як правило, менш поширений, ніж dictклас, його складніше серіалізувати IME.

Функції PS IMO | методи, не призначені для мутації об'єкта, не повинні мутувати об'єкт.


Не потрібно створювати новий об’єкт щоразу. Ви можете зробити так само легко defaultdict(lambda l=[]: l).
Artyer

6
Ніколи не робіть того, що пропонує @Artyer - змінні параметри за замовчуванням будуть вас кусати.
Брендон Хамперт

2

Ось кілька прикладів setdefault, щоб показати його корисність:

"""
d = {}
# To add a key->value pair, do the following:
d.setdefault(key, []).append(value)

# To retrieve a list of the values for a key
list_of_values = d[key]

# To remove a key->value pair is still easy, if
# you don't mind leaving empty lists behind when
# the last value for a given key is removed:
d[key].remove(value)

# Despite the empty lists, it's still possible to 
# test for the existance of values easily:
if d.has_key(key) and d[key]:
    pass # d has some values for key

# Note: Each value can exist multiple times!
"""
e = {}
print e
e.setdefault('Cars', []).append('Toyota')
print e
e.setdefault('Motorcycles', []).append('Yamaha')
print e
e.setdefault('Airplanes', []).append('Boeing')
print e
e.setdefault('Cars', []).append('Honda')
print e
e.setdefault('Cars', []).append('BMW')
print e
e.setdefault('Cars', []).append('Toyota')
print e

# NOTE: now e['Cars'] == ['Toyota', 'Honda', 'BMW', 'Toyota']
e['Cars'].remove('Toyota')
print e
# NOTE: it's still true that ('Toyota' in e['Cars'])

2

Я переписав прийняту відповідь і прийняв її для новачків.

#break it down and understand it intuitively.
new = {}
for (key, value) in data:
    if key not in new:
        new[key] = [] # this is core of setdefault equals to new.setdefault(key, [])
        new[key].append(value)
    else:
        new[key].append(value)


# easy with setdefault
new = {}
for (key, value) in data:
    group = new.setdefault(key, []) # it is new[key] = []
    group.append(value)



# even simpler with defaultdict
new = defaultdict(list)
for (key, value) in data:
    new[key].append(value) # all keys have a default value of empty list []

Крім того, я класифікував методи як довідкові:

dict_methods_11 = {
            'views':['keys', 'values', 'items'],
            'add':['update','setdefault'],
            'remove':['pop', 'popitem','clear'],
            'retrieve':['get',],
            'copy':['copy','fromkeys'],}

1

Я часто використовую setdefault, коли, отримайте це, встановивши за словом за замовчуванням (!!!); дещо поширений словник os.environ:

# Set the venv dir if it isn't already overridden:
os.environ.setdefault('VENV_DIR', '/my/default/path')

Менш лаконічно це виглядає приблизно так:

# Set the venv dir if it isn't already overridden:
if 'VENV_DIR' not in os.environ:
    os.environ['VENV_DIR'] = '/my/default/path')

Варто зазначити, що ви також можете використовувати отриману змінну:

venv_dir = os.environ.setdefault('VENV_DIR', '/my/default/path')

Але це менш необхідно, ніж це було до існування за замовчуваннямдиктів.


1

Ще один випадок використання, який, на мою думку, не згадувався вище. Іноді ви зберігаєте кеш-пам'ять об'єктів за їх ідентифікатором, де основний екземпляр знаходиться в кеші, і ви хочете встановити кеш, коли його немає.

return self.objects_by_id.setdefault(obj.id, obj)

Це корисно, коли ви завжди хочете зберегти один екземпляр на окремий ідентифікатор незалежно від того, як ви отримуєте obj кожен раз. Наприклад, коли атрибути об'єкта оновлюються в пам'яті і зберігається в сховищі.


1

Один дуже важливий випадок використання, на який я щойно наткнувся: dict.setdefault()чудово підходить для багатопотокового коду, коли потрібно лише один канонічний об'єкт (на відміну від кількох об'єктів, які мають рівну кількість).

Наприклад, в (Int)FlagEnum в Python 3.6.0 є помилка : якщо за складений (Int)Flagчлен змагаються кілька потоків , їх може бути більше одного:

from enum import IntFlag, auto
import threading

class TestFlag(IntFlag):
    one = auto()
    two = auto()
    three = auto()
    four = auto()
    five = auto()
    six = auto()
    seven = auto()
    eight = auto()

    def __eq__(self, other):
        return self is other

    def __hash__(self):
        return hash(self.value)

seen = set()

class cycle_enum(threading.Thread):
    def run(self):
        for i in range(256):
            seen.add(TestFlag(i))

threads = []
for i in range(8):
    threads.append(cycle_enum())

for t in threads:
    t.start()

for t in threads:
    t.join()

len(seen)
# 272  (should be 256)

Рішення полягає у використанні setdefault()в якості останнього кроку збереження обчисленого складеного елемента - якщо інший уже збережений, він використовується замість нового, гарантуючи унікальні члени Enum.


0

[Редагувати] Дуже неправильно! Налаштування за замовчуванням завжди викликає long_computation, Python виявляється нетерплячим.

Розгортання відповіді Туттла. Для мене найкращим випадком використання є кеш-механізм. Замість:

if x not in memo:
   memo[x]=long_computation(x)
return memo[x]

який споживає 3 рядки та 2 або 3 пошуку, я б із задоволенням написав :

return memo.setdefault(x, long_computation(x))

Хороший приклад. Я все ще думаю, що 3 рядки є більш зрозумілими, але, можливо, мій мозок виросте, щоб оцінити встановлені за замовчуванням.
Боб Штейн

5
Вони не є рівнозначними. По-перше, long_computation(x)називається лише якщо x not in memo. Тоді як у другому long_computation(x)завжди називається. Тільки призначення є умовним, еквівалентний код setdefaultвиглядатиме так: v = long_computation(x)/ if x not in memo:/ memo[x] = v.
Ден Д.

0

Мені подобається відповідь, дана тут:

http://stupidpythonideas.blogspot.com/2013/08/defaultdict-vs-setdefault.html

Коротше кажучи, рішення (у критичних для продуктивності додатках) слід приймати виходячи з того, як потрібно обробляти пошук порожніх клавіш вниз за течією ( а саме KeyError проти значення за замовчуванням).


0

Інший випадок використання - setdefault()це коли ви не хочете перезаписувати значення вже встановленого ключа. defaultdictперезаписує, а setdefault()ні. Для вкладених словників частіше буває так, що ви хочете встановити за замовчуванням, лише якщо ключ ще не встановлений, тому що ви не хочете видаляти даний під словник. Це коли ви використовуєте setdefault().

Приклад із defaultdict:

>>> from collection import defaultdict()
>>> foo = defaultdict()
>>> foo['a'] = 4
>>> foo['a'] = 2
>>> print(foo)
defaultdict(None, {'a': 2})

setdefault не перезаписує:

>>> bar = dict()
>>> bar.setdefault('a', 4)
>>> bar.setdefault('a', 2)
>>> print(bar)
{'a': 4}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.