Почему setdefault не работает в словаре?

Почему setdefault не увеличивается на 1 для каждого вхождения в a внутри словарного понимания, но это делает в цикле? Что тут происходит?

Альтернативные решения великолепны. Мне в основном интересно понять, почему это не работает.

Цикл с установкой по умолчанию работает

a = [1,1,2,2,2,3,3]

b = {}

for x in a:
    b[x] = b.setdefault(x, 0) + 1

b

Out[4]: {1: 2, 2: 3, 3: 2}

Понимание словаря с помощью setdefault не работает

b = {k: b.setdefault(k, 0) + 1 for k in a}

b

Out[7]: {1: 1, 2: 1, 3: 1}

Обновить

Спасибо за ответы, я хотел попробовать найти решение.

def using_get(a):
    b = {}
    for x in a:
        b[x] = b.get(x, 0) + 1
    return b


def using_setdefault(a):
    b = {}
    for x in a:
        b[x] = b.setdefault(x, 0) + 1
    return b


timeit.timeit(lambda: Counter(a), number=1000000)
Out[3]: 15.19974103783569

timeit.timeit(lambda: using_get(a), number=1000000)
Out[4]: 3.1597984457950474

timeit.timeit(lambda: using_setdefault(a), number=1000000)
Out[5]: 3.231248461129759

3 ответа

Решение

Там нет словарь еще в понимании dict. Вы создаете совершенно новый словарь, заменяя все b был обязан раньше.

Другими словами, в вашем словаре понимание, b.setdefault() это совершенно другой словарь, он не имеет ничего общего с объектом, создаваемым пониманием.

На самом деле, ваше понимание словаря работает только если b был привязан к объекту с .setdefault() Метод, прежде чем запустить выражение. Если b еще не определен или не привязан к объекту с помощью такого метода, он просто завершается ошибкой с исключением:

>>> a = [1,1,2,2,2,3,3]
>>> b = {k: b.setdefault(k, 0) + 1 for k in a}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <dictcomp>
NameError: global name 'b' is not defined
>>> b = 42
>>> b = {k: b.setdefault(k, 0) + 1 for k in a}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <dictcomp>
AttributeError: 'int' object has no attribute 'setdefault'

Вы не можете делать то, что вы хотите с пониманием словаря, если вы не группируете свои числа, что требует сортировки и itertools.groupby(); это не эффективный подход (требующий O(NlogN) шагов, а не O(N)):

>>> from itertools import groupby
>>> {k: sum(1 for _ in group) for k, group in groupby(sorted(a))}
{1: 2, 2: 3, 3: 2}

Обратите внимание, что стандартная библиотека уже поставляется с инструментом для подсчета; увидеть collections.Counter() объект:

>>> from collections import Counter
>>> Counter(a)
Counter({2: 3, 1: 2, 3: 2})

Это не работает, потому что b не определено до завершения понимания словаря. Обычно вы должны получить NameError за это; если нет, то потому что вы уже определили b раньше, но это будет другой словарь.

Сказав это: кажется, что вы можете просто использовать collections.Counter за это.

>>> a = [1,1,2,2,2,3,3]
>>> collections.Counter(a)
Counter({2: 3, 1: 2, 3: 2})

На самом деле, ваш второй фрагмент поднимает NameError если вы попробуете это в чистом пространстве имен (то, где нет предварительного определения b):

bruno@bigb:~/Work/playground$ python
Python 2.7.3 (default, Jun 22 2015, 19:33:41) 
>>> a = [1,1,2,2,2,3,3]
>>> b = {k: b.setdefault(k, 0) + 1 for k in a}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <dictcomp>
NameError: global name 'b' is not defined

Что должно дать вам подсказку о том, что пошло не так.

Заявление:

b = {k: b.setdefault(k, 0) + 1 for k in a}

сначала оценивает (ну, на самом деле пытается...) выражение правой части {k: b.setdefault(k, 0) + 1 for k in a}, а затем связывает результат с именем b,

Если b не определено, когда выражение eval'd, вы получите вышеупомянутое исключение (конечно). Если он определен и привязан к диктату (или тому, что имеет setdefault(x, y) метод FWIW) вы получите результат вызова setdefault() на что бы то ни было b связан в этой точке.

Другие вопросы по тегам