88import collections
99import functools
1010
11+ from python_toolbox import math_tools
12+
1113from .lazy_tuple import LazyTuple
1214from .ordered_dict import OrderedDict
1315from .frozen_dict_and_frozen_ordered_dict import FrozenDict , FrozenOrderedDict
@@ -21,6 +23,33 @@ def _count_elements(mapping, iterable):
2123 mapping_get = mapping .get
2224 for element in iterable :
2325 mapping [element ] = mapping_get (element , 0 ) + 1
26+
27+
28+ class _NO_DEFAULT :
29+ pass
30+
31+ class _ZeroCountAttempted (Exception ):
32+ pass
33+
34+
35+ def _process_count (count ):
36+ if not math_tools .is_integer (count ):
37+ raise TypeError (
38+ 'You passed %s as the count of %s, while a `Bag` can only handle '
39+ 'integer counts.' % (count , key )
40+ )
41+ if count < 0 :
42+ raise TypeError (
43+ "You passed %s as the count of %s, while `Bag` doesn't support "
44+ "negative amounts." % (count , key )
45+ )
46+
47+ if count == 0 :
48+ raise _ZeroCountAttempted
49+
50+ return int (count )
51+
52+
2453
2554class _BaseBagMixin :
2655 '''Mixin for `FrozenBag` and `FrozenOrderedBag`.'''
@@ -32,23 +61,10 @@ def __init__(self, iterable={}):
3261
3362 if isinstance (iterable , collections .Mapping ):
3463 for key , value , in iterable .items ():
35- if not math_tools .is_integer (value ):
36- raise TypeError (
37- 'You passed %s as the count of %s, while '
38- 'a `Bag` can only handle integer counts.' %
39- (value , key )
40- )
41- if value < 0 :
42- raise TypeError (
43- "You passed %s as the count of %s, while "
44- "`Bag` doesn't support negative amounts." %
45- (value , key )
46- )
47-
48- if value == 0 :
64+ try :
65+ self ._dict [key ] = _process_count (value )
66+ except _ZeroCountAttempted :
4967 continue
50-
51- self ._dict [key ] = int (value )
5268 else :
5369 _count_elements (self ._dict , iterable )
5470
@@ -291,8 +307,52 @@ def __repr__(self):
291307
292308class _MutableBagMixin (_BaseBagMixin ):
293309 # blocktodo: ensure all mutable methods, like __iadd__ and everything
294- pass
310+
311+ def __setitem__ (self , i , count ):
312+ try :
313+ super ().__setitem__ (i , _process_count (count ))
314+ except _ZeroCountAttempted :
315+ del self [i ]
316+
317+
318+ def __imod__ (self , modulo ):
319+ for key in tuple (self .keys ()):
320+ self [key ] %= modulo
321+ return self
322+
323+ def setdefault (self , key , default ):
324+ current_count = self [key ]
325+ if current_count > 0 :
326+ return current_count
327+ else :
328+ self [key ] = default
329+ return default
330+
331+ def __delitem__ (self , key ):
332+ # We're making `__delitem__` not raise an exception on missing or
333+ # zero-count elements because we're automatically deleting zero-count
334+ # elements even though they seem to exist from the outside, so we're
335+ # avoiding raising exceptions where someone would try to explicitly
336+ # delete them.
337+ try :
338+ del self ._dict [key ]
339+ except KeyError :
340+ pass
295341
342+ def pop (self , key , default = _NO_DEFAULT ):
343+ '''D.pop(k[,d]) -> v, remove specified key and return the corresponding value.
344+ If key is not found, d is returned if given, otherwise KeyError is raised.
345+ '''
346+ value = self [key ]
347+ if value == 0 and default is not _NO_DEFAULT :
348+ return default
349+ else :
350+ del self [key ]
351+ return value
352+
353+
354+
355+
296356class _OrderedBagMixin (Ordered ):
297357 def __repr__ (self ):
298358 if not self :
0 commit comments