Coverage for trait.py: 91%
149 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-08 21:58 +0200
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-08 21:58 +0200
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3"""traits of wits for MutableMapping
4(any class that quacks like a dict, key like dict, and fly like a dict).
5You'll make your code perspire smartness by all its pore(c)(tm)(r).
6"""
7from __future__ import division
8try:
9 from collections import MutableMapping, Mapping
10except ImportError:
11 from collections.abc import MutableMapping, Mapping
13from .barrack import paired_row_iter, mapping_row_iter, bowyer
14__all__ = [ 'InclusiveAdder', 'InclusiveSubber', "Vector",
15 'ExclusiveMuler', 'TaintedExclusiveDiver',
16 'Copier', 'Iterator', 'Searchable']
17from copy import deepcopy
18class Copier(object):
19 def copy(self):
20 try:
21 return deepcopy(
22 bowyer(
23 globals()[type(self).__name__],
24 {k:v for k,v in self.items()}
25 )
26 )
27 except KeyError:
28 pass
29 if hasattr(self,"_asdict"):
30 return self._asdict().copy()
31 if hasattr(super(Copier, self),"copy"):
32 return self.__class__(super(Copier, self).copy())
33 return deepcopy(self)
35class Vector(object):
37 def dot(u, v):
38 """
39 scalar product of two MappableMappings (recursive)
40 https://en.wikipedia.org/wiki/Dot_product
42 """
43 return sum(v for i,v in paired_row_iter(u*v))
46 def __abs__(v):
47 """return the absolute value (hence >=0)
48 aka the distance from origin as defined in Euclidean geometry.
49 Keys of the dict are the dimension, values are the metrics
50 https://en.wikipedia.org/wiki/Euclidean_distance
51 """
52 return v.dot(v)**.5
54 def cos(u, v):
55 """
56 returns the cosine similarity of 2 mutable mappings (recursive)
57 https://en.wikipedia.org/wiki/Cosine_similarity
58 dict().cos(dict(x=....)) will logically yield division by 0 exception.
59 http://math.stackexchange.com/a/932454
60 """
61 return u.dot(v) / abs(u) / abs(v)
63from copy import deepcopy
65class InclusiveAdder(object):
66 """ making dict able to add
68 >>> from archery.trait import Adder
69 >>> from collections import defaultdict
70 >>> class dad(defaultdict,Adder,Subber):pass
71 ...
72 >>> tata = dad( int, dict(a = 1, b = 0, c = -1 ) )
73 >>>
74 >>> toto = dad( int, dict(a = 1 ) )
75 >>> print toto+tata
76 defaultdict(<type 'int'>, {'a': 2, 'c': -1, 'b': 0})
77 >>> toto+=1
78 defaultdict(<type 'int'>, {'a': 2}
80"""
82 def __add__(self, other):
83 """adder"""
84 copy = self.copy()
85 copy += other
86 return copy
88 def __iinc__(self, number):
89 """in place increment"""
90 for k in self.keys():
91 self[k] += number
92 return self
94 def __iadd__(self, other):
95 if not isinstance(other, MutableMapping):
96 return self.__iinc__(other)
97 for k, v in other.items():
98 if k in self:
99 self[k] += v
100 else:
101 self[k] = v
102 return self
104 def __radd__(self, other):
105 copy = self.copy()
106 return copy + other
109class TaintedExclusiveDiver(object):
110 """Making dict able to truedivide (you need to provide a muler)
111 This operator is tainted thanks to my inability to make neither
112 from __future__ import truedivision
113 nor
114 from operator import truetruediv
115 works.
116 So as a result I use implicit 1.0 cast
118 Why don't I stick to regular (broken) python truedivision ?
119 Don't you think this :
120 0.5 * a == a / 2
121 less surprising than :
122 a/2 = something that is part /2 (if result is int),
123 and sometimes something else
125 )"""
126 def __div__(self, other):
127 return self.__truediv__(other)
129 def __idiv__(self, other):
130 return self.__itruediv__(other)
132 def __rdiv__(self, other):
133 return self.__rtruediv__(other)
135 def __truediv__(self, other):
136 """truediver"""
137 copy = self.copy()
138 if not isinstance(other, MutableMapping):
139 return copy.__iscalmul__(1 / other)
140 copy /= other
141 return copy
143 def __itruediv__(self, other):
144 if not isinstance(other, MutableMapping):
145 self.__iscalmul__(1 / other)
146 return self
147 todel = []
148 for k in self:
149 if k in other:
150 self[k] /= other[k]
151 else:
152 todel += [k]
153 for k in todel:
154 del(self[k])
155 return self
157 def __iinv__(self):
158 """in place inversion 1/a"""
159 for k, v in self.items():
160 self[k] = 1/v
161 return self
163 def __rtruediv__(self, other):
164 copy = self.copy()
165 if not isinstance(other, MutableMapping):
166 return copy.__iinv__().__iscalmul__(other)
167 return copy / other
170class ExclusiveMuler(object):
171 """Making dict able to multiply"""
173 def __mul__(self, other):
174 """muler"""
175 copy = self.copy()
176 copy *= other
177 return copy
179 def __iscalmul__(self, number):
180 """in place imul"""
181 for k in self.keys():
182 self[k] *= number
183 return self
185 def __imul__(self, other):
186 if not isinstance(other, MutableMapping):
187 self.__iscalmul__(other)
188 return self
189 todel = set()
190 for k in self:
191 if k in other:
192 self[k] *= other[k]
193 else:
194 todel |= {k,}
195 for k in todel:
196 del(self[k])
197 return self
199 def __rmul__(self, other):
200 copy = self.copy()
201 if not isinstance(other, MutableMapping):
202 return copy.__iscalmul__(other)
203 return copy.__mul__(other)
205 def __lmul__(self, other):
206 copy = self.copy()
207 if not isinstance(other, MutableMapping):
208 return copy.__iscalmul__(other)
209 return copy.__mul__(other)
211class Iterator(object):
213 def __iter__(self):
214 self.__iter = mapping_row_iter(self)
215 return self.__iter
217 def __next__(self):
218 return self.__iter()
220class InclusiveSubber(object):
221 def __sub__(self, other):
222 """suber"""
223 copy = self.copy()
224 copy -= other
225 return copy
227 def __isub__(self, other):
228# breaks consistency
229# for k in self.keys():
230# self[k] -= other
231 if not isinstance(other, MutableMapping):
232 self.__iinc__(-other)
233 return self
234 for k, v in other.items():
235 self[k] = self[k] - v if k in self else -v
237 return self
239 def __rsub__(self, other):
240 copy = self.copy()
241 if not isinstance(other, MutableMapping):
242 return copy.__neg__().__iinc__(other)
243 return copy.__sub__(other)
245 def __neg__(self):
246 """in place negation"""
247 for k in self:
248 self[k] *= -1
249 return self
252class Searchable(Iterator):
255 def search(self, predicate):
256 """Return a generator of all tuples made of :
257 - all keys leading to a value
258 - and the value itself
259 that match the predicate on the `Path`_"""
260 for el in self:
261 if predicate(el):
262 yield el
264 def leaf_search(self, predicate):
265 """Return a generator all all values matching
266 the predicates"""
267 for el in self:
268 value = el[-1]
269 if predicate(value):
270 yield value