Почему 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
связан в этой точке.