Использование defaultdict для замены операторов try и/или if в python

Недавно я нашел и начал использовать словари по умолчанию для замены еще нескольких громоздких конструкций. Я читал в «дзен питона», что одним из ключевых моментов питона является «Должен быть один — и желательно только один — очевидный способ сделать это».

Основываясь на этих критериях (или, возможно, более практично на основе использования памяти или скорости), какой из следующих (или что-то совершенно другое) будет лучшим? У меня есть предчувствие, что первое верно, но хотелось бы узнать мнение других людей.

my_dict = defaultdict(int)
for generic in iterable:
    my_dict[generic] +=1

or:

my_dict = {}
for generic in iterable:
    if generic not in my_dict:
        my_dict[generic] = 1
    else:
        my_dict[generic]+=1

or:

my_dict = {}
for generic in iterable:
    try:
        my_dict[generic] += 1
    except(KeyError):
        my_dict[generic] = 1

То же самое можно сказать об использовании my_dict = defaultdict(list) и использовании функций добавления. Предположим, что используются несколько циклов for или другие условные выражения, а не просто подсчет общих значений из одной итерации, поскольку это, очевидно, будет иметь разные функции.


person ded    schedule 18.12.2013    source источник
comment
Для того, что вы здесь делаете, используйте collections.Counter.   -  person Paulo Almeida    schedule 18.12.2013
comment
defaultdict - лучший из трех, но, как сказал @PauloAlmeida, лучше использовать Counter, если вы используете только целые числа. Используйте defaultdict, когда вы действительно хотите вызывать методы типа по умолчанию d=defaultdict(list); d[0].extend([1,2,3]) и т. д.   -  person ejrb    schedule 18.12.2013


Ответы (2)


Как прокомментировал Пауло Алмейда, для примера, который вы опубликовали, «очевидным» решением является использование collections.Counter:

from collections import Counter
my_dict = Counter(iterable)

Вот и все.

Что касается других фрагментов, которые вы опубликовали, и если предположить, что my_dict[key] += 1 был просто для примера, а ваш общий вопрос касается «как лучше всего заполнить диктовку»: collections.defaultdict - правильный выбор для однородных диктовок (один и тот же тип значений для всех ключей), где тип имеет значение по умолчанию (числовой ноль, пустая строка, пустой список...). Наиболее распространенный вариант использования, о котором я могу думать, - это заполнение списка списков (или наборов или других контейнеров).

Теперь, когда ни collections.Counter, ни collections.defaultdict не решают вашу проблему, у вас есть три возможных шаблона:

  • смотреть перед
  • попробовать/кроме KeyError
  • dict.setdefault(key, value)

Решение try/except будет быстрее, если вы ожидаете, что ключ уже существует - блок try/except очень быстро настраивается, но требует больших затрат, когда возникает исключение. Насколько я понимаю, я не рекомендую это, если вы не очень-очень уверены в том, как ваши данные выглядят сейчас и как они будут выглядеть в будущем .

Решение «заглянуть раньше» имеет почти постоянную стоимость, и хотя оно и не бесплатное, но все же довольно дешевое. Это действительно ваша самая безопасная ставка.

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

person bruno desthuilliers    schedule 18.12.2013
comment
Для будущих читателей: в Python 2.7 try/except будет работать лучше, чем LBYL и dict.get, если вы ожидаете, что менее 25% ключей в совокупности не сработают. (Если в среднем каждая запись будет срабатывать не менее четырех раз, это будет быстрее.) EAFP работает быстрее и становится только быстрее по мере увеличения этой скорости (при частоте промахов 1%, почти на 40%). При этом defaultdict out выполняет EAFP во всех случаях с приличным отрывом (10-20%). См. repl.it. - person TemporalWolf; 16.02.2017

Если вы настаиваете на использовании словаря или defaultdict, лучше всего использовать первый. Однако для подсчета в коллекциях есть прекрасный класс Counter:

>>> from collections import Counter
>>> c = Counter()
>>> for generic in iterable:
...     c.update(generic)

Или еще короче:

>>> c = Counter(iterable)
person Noctua    schedule 18.12.2013