@@ -21,7 +21,7 @@ def _count_elements(mapping, iterable):
2121 for element in iterable :
2222 mapping [element ] = mapping_get (element , 0 ) + 1
2323
24- class _FrozenTallyMixin :
24+ class _BaseTallyMixin :
2525 '''Mixin for `FrozenTally` and `FrozenOrderedTally`.'''
2626
2727 def __init__ (self , iterable = {}):
@@ -40,7 +40,7 @@ def __init__(self, iterable={}):
4040 if value < 0 :
4141 raise TypeError (
4242 "You passed %s as the count of %s, while "
43- "a `Tally` doesn't support negative amounts." %
43+ "`Tally` doesn't support negative amounts." %
4444 (value , key )
4545 )
4646
@@ -107,7 +107,7 @@ def __add__(self, other):
107107 FrozenTally({'b': 4, 'c': 2, 'a': 1})
108108
109109 '''
110- if not isinstance (other , _FrozenTallyMixin ):
110+ if not isinstance (other , _BaseTallyMixin ):
111111 return NotImplemented
112112
113113 # Using `OrderedDict` to store interim results because
@@ -133,7 +133,7 @@ def __sub__(self, other):
133133 FrozenTally({'b': 2, 'a': 1})
134134
135135 '''
136- if not isinstance (other , _FrozenTallyMixin ):
136+ if not isinstance (other , _BaseTallyMixin ):
137137 return NotImplemented
138138
139139 # Using `OrderedDict` to store interim results because
@@ -154,7 +154,7 @@ def __or__(self, other):
154154 FrozenTally({'b': 3, 'c': 2, 'a': 1})
155155
156156 '''
157- if not isinstance (other , _FrozenTallyMixin ):
157+ if not isinstance (other , _BaseTallyMixin ):
158158 return NotImplemented
159159
160160 # Using `OrderedDict` to store interim results because
@@ -181,7 +181,7 @@ def __and__(self, other):
181181 FrozenTally({'b': 1})
182182
183183 '''
184- if not isinstance (other , _FrozenTallyMixin ):
184+ if not isinstance (other , _BaseTallyMixin ):
185185 return NotImplemented
186186
187187 # Using `OrderedDict` to store interim results because
@@ -227,7 +227,7 @@ def frozen_tally_tally(self):
227227 # takes the items' order into account.
228228
229229 def __lt__ (self , other ):
230- if not isinstance (other , _FrozenTallyMixin ):
230+ if not isinstance (other , _BaseTallyMixin ):
231231 return NotImplemented
232232 found_strict_difference = False # Until challenged.
233233 for element , count in self .items ():
@@ -242,7 +242,7 @@ def __lt__(self, other):
242242 return found_strict_difference
243243
244244 def __le__ (self , other ):
245- if not isinstance (other , _FrozenTallyMixin ):
245+ if not isinstance (other , _BaseTallyMixin ):
246246 return NotImplemented
247247 for element , count in self .items ():
248248 try :
@@ -254,7 +254,7 @@ def __le__(self, other):
254254 return True
255255
256256 def __gt__ (self , other ):
257- if not isinstance (other , _FrozenTallyMixin ):
257+ if not isinstance (other , _BaseTallyMixin ):
258258 return NotImplemented
259259 found_strict_difference = False # Until challenged.
260260 for element , count in self .items ():
@@ -269,7 +269,7 @@ def __gt__(self, other):
269269 return found_strict_difference
270270
271271 def __ge__ (self , other ):
272- if not isinstance (other , _FrozenTallyMixin ):
272+ if not isinstance (other , _BaseTallyMixin ):
273273 return NotImplemented
274274 for element , count in self .items ():
275275 try :
@@ -280,8 +280,65 @@ def __ge__(self, other):
280280 return False
281281 return True
282282
283+
284+ class _TallyMixin (_BaseTallyMixin ):
285+ # blocktodo: add all mutable methods, like __iadd__ and everything
286+ pass
287+
288+
289+ class _OrderedTallyMixin :
290+ def __repr__ (self ):
291+ if not self :
292+ return '%s()' % type (self ).__name__
293+ return '%s(%s)' % (
294+ type (self ).__name__ ,
295+ '[%s]' % ', ' .join ('%s' % (item ,) for item in self .items ())
296+ )
297+
298+
299+ class Tally (_TallyMixin , FrozenDict ):
300+ '''
301+ blocktododoc
302+ An immutable tally.
303+
304+ A tally that (a) can't be changed and (b) has only positive integer values.
305+ This is like `collections.Counter`, except:
306+
307+ - Because it's immutable, it's also hashable, and thus it can be used as a
308+ key in dicts and sets.
309+
310+ - Unlike `collections.Counter`, we don't think of it as a "dict who
311+ happens to count objects" but as an object that is absolutely intended
312+ for counting objects. This means we do not allow arbitrary values for
313+ counts like `collections.Counter` and we don't have to deal with a
314+ class that has an identity crisis and doesn't know whether it's a
315+ counter or a dict.
316+
317+ '''
318+
319+ class OrderedTally (_TallyMixin , FrozenDict ):
320+ '''
321+ blocktododoc
322+ An immutable, ordered tally.
323+
324+ A tally that (a) can't be changed and (b) has only positive integer values.
325+ This is like `collections.Counter`, except:
326+
327+ - Because it's immutable, it's also hashable, and thus it can be used as a
328+ key in dicts and sets.
329+
330+ - Unlike `collections.Counter`, we don't think of it as a "dict who
331+ happens to count objects" but as an object that is absolutely intended
332+ for counting objects. This means we do not allow arbitrary values for
333+ counts like `collections.Counter` and we don't have to deal with a
334+ class that has an identity crisis and doesn't know whether it's a
335+ counter or a dict.
336+
337+ - It has an order to its elements, like `collections.OrderedDict`.
338+
339+ '''
283340
284- class FrozenTally (_FrozenTallyMixin , FrozenDict ):
341+ class FrozenTally (_BaseTallyMixin , FrozenDict ):
285342 '''
286343 An immutable tally.
287344
@@ -300,7 +357,8 @@ class that has an identity crisis and doesn't know whether it's a
300357
301358 '''
302359
303- class FrozenOrderedTally (_FrozenTallyMixin , FrozenOrderedDict ):
360+ class FrozenOrderedTally (_OrderedTallyMixin , _BaseTallyMixin ,
361+ FrozenOrderedDict ):
304362 '''
305363 An immutable, ordered tally.
306364
0 commit comments