Skip to content

Commit 88b88a9

Browse files
committed
-
1 parent 0db9232 commit 88b88a9

File tree

3 files changed

+139
-26
lines changed

3 files changed

+139
-26
lines changed

source_py3/python_toolbox/nifty_collections/bagging.py

Lines changed: 89 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,77 @@ def _process_count(count):
6060
return int(count)
6161

6262

63+
class _BootstrappedCachedProperty(misc_tools.OwnNameDiscoveringDescriptor):
64+
'''
65+
A property that is calculated only once for an object, and then cached.
66+
67+
This is defined here in `bagging.py` because we can't import the canonical
68+
`CachedProperty` because of an import loop.
69+
70+
Usage:
71+
72+
class MyObject:
73+
74+
# ... Regular definitions here
75+
76+
def _get_personality(self):
77+
print('Calculating personality...')
78+
time.sleep(5) # Time consuming process that creates personality
79+
return 'Nice person'
80+
81+
personality = CachedProperty(_get_personality)
82+
83+
You can also put in a value as the first argument if you'd like to have it
84+
returned instead of using a getter. (It can be a tobag static value like
85+
`0`). If this value happens to be a callable but you'd still like it to be
86+
used as a static value, use `force_value_not_getter=True`.
87+
'''
88+
def __init__(self, getter_or_value, doc=None, name=None,
89+
force_value_not_getter=False):
90+
'''
91+
Construct the cached property.
92+
93+
`getter_or_value` may be either a function that takes the parent object
94+
and returns the value of the property, or the value of the property
95+
itself, (as long as it's not a callable.)
96+
97+
You may optionally pass in the name that this property has in the
98+
class; this will save a bit of processing later.
99+
'''
100+
misc_tools.OwnNameDiscoveringDescriptor.__init__(self, name=name)
101+
if callable(getter_or_value) and not force_value_not_getter:
102+
self.getter = getter_or_value
103+
else:
104+
self.getter = lambda thing: getter_or_value
105+
self.__doc__ = doc or getattr(self.getter, '__doc__', None)
106+
107+
108+
def __get__(self, obj, our_type=None):
109+
110+
if obj is None:
111+
# We're being accessed from the class itself, not from an object
112+
return self
113+
114+
value = self.getter(obj)
115+
116+
setattr(obj, self.get_our_name(obj, our_type=our_type), value)
117+
118+
return value
119+
120+
121+
def __call__(self, method_function):
122+
'''
123+
Decorate method to use value of `CachedProperty` as a context manager.
124+
'''
125+
def inner(same_method_function, self_obj, *args, **kwargs):
126+
with getattr(self_obj, self.get_our_name(self_obj)):
127+
return method_function(self_obj, *args, **kwargs)
128+
return decorator_tools.decorator(inner, method_function)
129+
130+
131+
def __repr__(self):
132+
return '<%s: %s>' % (type(self).__name__, self.our_name or self.getter)
133+
63134

64135
class _BaseBagMixin:
65136
'''Mixin for `FrozenBag` and `FrozenOrderedBag`.'''
@@ -125,6 +196,13 @@ def elements(self):
125196
def __contains__(self, item):
126197
return (self[item] >= 1)
127198

199+
n_elements = property(lambda self: sum(self.values()))
200+
201+
@property
202+
def frozen_bag_bag(self):
203+
from .frozen_bag_bag import FrozenBagBag
204+
return FrozenBagBag(self.values())
205+
128206
def __or__(self, other):
129207
'''
130208
Get the maximum of value in either of the input bags.
@@ -248,28 +326,6 @@ def __pow__(self, other, modulo=None):
248326
__bool__ = lambda self: any(True for element in self.elements())
249327

250328

251-
_n_elements = None
252-
@property
253-
def n_elements(self):
254-
# Implemented as a poor man's `CachedProperty` because we can't use the
255-
# real `CachedProperty` due to circular import.
256-
if self._n_elements is None:
257-
self._n_elements = sum(self.values())
258-
return self._n_elements
259-
_n_elements = None
260-
261-
262-
_frozen_bag_bag = None
263-
@property
264-
def frozen_bag_bag(self):
265-
# Implemented as a poor man's `CachedProperty` because we can't use the
266-
# real `CachedProperty` due to circular import.
267-
from .frozen_bag_bag import FrozenBagBag
268-
if self._frozen_bag_bag is None:
269-
self._frozen_bag_bag = FrozenBagBag(self.values())
270-
return self._frozen_bag_bag
271-
272-
273329
# We define all the comparison methods manually instead of using
274330
# `total_ordering` because `total_ordering` assumes that >= means (> and
275331
# ==) while we, in `FrozenOrderedBag`, don't have that hold because ==
@@ -492,6 +548,15 @@ def __eq__(self, other):
492548
index = misc_tools.ProxyProperty('._dict.index')
493549

494550

551+
class _FrozenBagMixin:
552+
553+
n_elements = _BootstrappedCachedProperty(lambda self: sum(self.values()))
554+
555+
@_BootstrappedCachedProperty
556+
def frozen_bag_bag(self):
557+
from .frozen_bag_bag import FrozenBagBag
558+
return FrozenBagBag(self.values())
559+
495560

496561

497562
class _BaseDictDelegator(collections.MutableMapping):
@@ -600,7 +665,7 @@ def popitem(self, last=True):
600665

601666

602667

603-
class FrozenBag(_BaseBagMixin, FrozenDict):
668+
class FrozenBag(_BaseBagMixin, _FrozenBagMixin, FrozenDict):
604669
'''
605670
An immutable bag that counts items.
606671
@@ -626,7 +691,7 @@ def __hash__(self):
626691
return hash((type(self), frozenset(self.items())))
627692

628693

629-
class FrozenOrderedBag(_OrderedBagMixin, _BaseBagMixin,
694+
class FrozenOrderedBag(_OrderedBagMixin, _FrozenBagMixin, _BaseBagMixin,
630695
FrozenOrderedDict):
631696
'''
632697
An immutable, ordered bag that counts items.

source_py3/test_python_toolbox/test_nifty_collections/test_bagging.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,19 @@ def test_bool(self):
9191
assert not bag
9292

9393

94+
def test_n_elements(self):
95+
bag = self.bag_type('meow')
96+
assert bag.n_elements == 4
97+
assert bag.n_elements == 4 # Testing again because now it's a data
98+
# attribute.
99+
if not isinstance(bag, collections.Hashable):
100+
bag['x'] = 1
101+
assert bag.n_elements == 5
102+
assert bag.n_elements == 5
103+
104+
105+
106+
94107
def test_no_visible_dict(self):
95108
bag = self.bag_type('abc')
96109
with cute_testing.RaiseAssertor(AttributeError):
@@ -226,6 +239,42 @@ def test_sort(self):
226239
bag = self.bag_type('aaabbc')
227240
with cute_testing.RaiseAssertor(AttributeError):
228241
bag.sort()
242+
243+
def test_unsupported_operations(self):
244+
bag = self.bag_type('meeeeow')
245+
with cute_testing.RaiseAssertor(TypeError): bag | 'foo'
246+
with cute_testing.RaiseAssertor(TypeError): 'foo' | bag
247+
with cute_testing.RaiseAssertor(TypeError): bag & 'foo'
248+
with cute_testing.RaiseAssertor(TypeError): 'foo' & bag
249+
with cute_testing.RaiseAssertor(TypeError): bag + 'foo'
250+
with cute_testing.RaiseAssertor(TypeError): 'foo' + bag
251+
with cute_testing.RaiseAssertor(TypeError): bag - 'foo'
252+
with cute_testing.RaiseAssertor(TypeError): 'foo' - bag
253+
with cute_testing.RaiseAssertor(TypeError): bag * 'foo'
254+
with cute_testing.RaiseAssertor(TypeError): 'foo' * bag
255+
with cute_testing.RaiseAssertor(TypeError): bag / 'foo'
256+
with cute_testing.RaiseAssertor(TypeError): 'foo' / bag
257+
with cute_testing.RaiseAssertor(TypeError): bag // 'foo'
258+
with cute_testing.RaiseAssertor(TypeError): 'foo' // bag
259+
with cute_testing.RaiseAssertor(TypeError): bag % 'foo'
260+
with cute_testing.RaiseAssertor(TypeError): 3 % bag
261+
with cute_testing.RaiseAssertor(TypeError): bag ** 'foo'
262+
with cute_testing.RaiseAssertor(TypeError): 'foo' ** bag
263+
with cute_testing.RaiseAssertor(TypeError): divmod(bag, 'foo')
264+
with cute_testing.RaiseAssertor(TypeError): divmod('foo', bag)
265+
if not isinstance(bag, collections.Hashable):
266+
with cute_testing.RaiseAssertor(TypeError): bag |= 'foo'
267+
with cute_testing.RaiseAssertor(TypeError): bag &= 'foo'
268+
with cute_testing.RaiseAssertor(TypeError): bag += 'foo'
269+
with cute_testing.RaiseAssertor(TypeError): bag -= 'foo'
270+
with cute_testing.RaiseAssertor(TypeError): bag *= 'foo'
271+
with cute_testing.RaiseAssertor(TypeError): bag /= 'foo'
272+
with cute_testing.RaiseAssertor(TypeError): bag //= 'foo'
273+
with cute_testing.RaiseAssertor(TypeError): bag %= 'foo'
274+
with cute_testing.RaiseAssertor(TypeError): bag **= 'foo'
275+
276+
277+
229278

230279

231280
class BaseMutableBagTestCase(BaseBagTestCase):

source_py3/test_python_toolbox/test_proxy_property.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,12 @@
1111

1212

1313
class Object:
14-
''' '''
14+
pass
1515

1616

1717
def test():
1818

1919
class A:
20-
''' '''
2120
y = 'y'
2221
def __init__(self):
2322
self.x = 'x'

0 commit comments

Comments
 (0)