Ответ 1
Да, это ожидается; конструктор Counter()
использует Counter.update()
, который использует self.get()
для загрузки начальных значений, а не для __missing__
.
Кроме того, defaultdict
__missing__
factory полностью обрабатывается кодом C, особенно при использовании другого типа типа int()
, который сам реализован в C. Источник Counter
- это чистый Python, Для метода Counter.__missing__
требуется выполнить кадр Python.
Поскольку dict.get()
по-прежнему обрабатывается на C, подход конструктора является более быстрым подходом для Counter()
, если вы используете тот же трюк Counter.update()
и псевдоним поиска self.get
как локальный первый:
>>> import timeit
>>> import random
>>> to_count = [random.randint(0, 100) for r in range(60)]
>>> timeit.timeit('for i in to_count: c[i] += 1',
... 'from collections import Counter; from __main__ import to_count; c = Counter()',
... number=10000)
0.2510359287261963
>>> timeit.timeit('for i in to_count: c[i] = c_get(i, 0) + 1',
... 'from collections import Counter; from __main__ import to_count; c = Counter(); c_get = c.get',
... number=10000)
0.20978617668151855
Оба defaultdict
и Counter
- полезные классы, созданные для их функциональности, а не их производительность; не полагаясь на крюк __missing__
, может быть быстрее:
>>> timeit.timeit('for i in to_count: d[i] = d_get(i, 0) + 1',
... 'from __main__ import to_count; d = {}; d_get = d.get',
... number=10000)
0.11437392234802246
Это обычный словарь, использующий метод псевдонимов dict.get()
для максимальной скорости. Но тогда вам также придется повторно реализовать поведение мешка Counter
или метода Counter.most_common()
. Варианты использования defaultdict
выходят за рамки подсчета.
В Python 3.2 обновление Counter()
ускорилось, добавив библиотеку C, которая обрабатывает этот случай; см. номер 10667. Тестирование на Python 3.4, конструктор Counter()
теперь превосходит случай с псевдонимом dict.get
:
>>> timeit.timeit('Counter(to_count)',
... 'from collections import Counter; from __main__ import to_count',
... number=100000)
0.8332311600097455
>>> timeit.timeit('for i in to_count: d[i] = d_get(i, 0) + 1',
... 'from __main__ import to_count; d = {}; d_get = d.get',
... number=100000)
0.961191965994658