Объекты-счетчики являются подклассами dict, поэтому они имеют метод setdefault.
>>> from collections import Counter
>>> c = Counter(houses=5)
>>> print(c.setdefault.__doc__)
D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D
Если я сделаю:
>>> c.setdefault('castles')
>>> c.keys()
dict_keys(['castles', 'houses'])
>>> type(c)
<class 'collections.Counter'>
все кажется довольно приятным. Но:
>>> c
Traceback (most recent call last):
File "<pyshell#17>", line 1, in <module>
c
File "C:\Python32\lib\collections.py", line 586, in __repr__
items = ', '.join(map('%r: %r'.__mod__, self.most_common()))
File "C:\Python32\lib\collections.py", line 477, in most_common
return sorted(self.items(), key=_itemgetter(1), reverse=True)
TypeError: unorderable types: NoneType() < int()
>>>
Это ошибка?. Не следует ли давать c.setdefault('castles')
ошибку value/key вместо молчания принимать ключ без значения? Или, может быть, метод перечня с учетом значений None?
Да, похоже на ошибку. Проблема в том, что setdefault
без аргумента value предполагает, что значение равно None
, в то время как в случае с Counter
оно должно действительно вставить либо одно, либо ноль или сбой, вызвав некоторое исключение.
В Python 2.7 ваш фрагмент работает, кстати, хотя он все еще вставляет значение None
, нарушая инварианты Counter
.
Имейте в виду, что это не первый дефект ошибки/дизайна, с которым я сталкиваюсь с collections.Counter
.
Линия c.setdefault('castles')
непосредственно присваивает c['castles'] = None
. Вероятно, это не то, что вы намеревались.
Если вы планируете создавать замки в __repr__, вместо этого используйте c['castles'] = 0
.
Для того, чтобы счетчик работал в соответствии с проектом, ключи могут быть любыми, что вы хотите подсчитать, а значения должны быть числом. Как вы видели, шаг сортировки в __repr__ ожидает, что значения являются всеми числами, и он не будет работать, если для одного из значений установлено значение None.
Может показаться, что setdefault будет использоваться для получения значений по умолчанию для счетчика или назначения заводской функции, но это не то, что делает setdefault. И вам не нужно делать этот шаг вообще, поскольку объекты Counter автоматически возвращают значение по умолчанию для нуля. Никакой дополнительной работы не требуется.
Вот как все это работает, просто и легко:
>>> from collections import Counter
>>> c = Counter(houses=5)
>>> c
Counter({'houses': 5})
>>> c['castles'] # counters automatically return zero for missing items, no work required
0
>>> c # but missing items won't show in the __repr__
Counter({'houses': 5})
>>> c['castles'] = 0 # unless you specifically add an entry for them
>>> c
Counter({'houses': 5, 'castles': 0})