Coverage for trait.py: 91%

149 statements  

« 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 

12 

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) 

34 

35class Vector(object): 

36 

37 def dot(u, v): 

38 """ 

39 scalar product of two MappableMappings (recursive) 

40 https://en.wikipedia.org/wiki/Dot_product 

41 

42 """ 

43 return sum(v for i,v in paired_row_iter(u*v)) 

44 

45 

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 

53 

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) 

62 

63from copy import deepcopy 

64 

65class InclusiveAdder(object): 

66 """ making dict able to add  

67 

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} 

79 

80""" 

81 

82 def __add__(self, other): 

83 """adder""" 

84 copy = self.copy() 

85 copy += other 

86 return copy 

87 

88 def __iinc__(self, number): 

89 """in place increment""" 

90 for k in self.keys(): 

91 self[k] += number 

92 return self 

93 

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 

103 

104 def __radd__(self, other): 

105 copy = self.copy() 

106 return copy + other 

107 

108 

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 

117  

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 

124 

125 )""" 

126 def __div__(self, other): 

127 return self.__truediv__(other) 

128 

129 def __idiv__(self, other): 

130 return self.__itruediv__(other) 

131 

132 def __rdiv__(self, other): 

133 return self.__rtruediv__(other) 

134 

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 

142 

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 

156 

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 

162 

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 

168 

169 

170class ExclusiveMuler(object): 

171 """Making dict able to multiply""" 

172 

173 def __mul__(self, other): 

174 """muler""" 

175 copy = self.copy() 

176 copy *= other 

177 return copy 

178 

179 def __iscalmul__(self, number): 

180 """in place imul""" 

181 for k in self.keys(): 

182 self[k] *= number 

183 return self 

184 

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 

198 

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) 

204 

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) 

210 

211class Iterator(object): 

212 

213 def __iter__(self): 

214 self.__iter = mapping_row_iter(self) 

215 return self.__iter 

216 

217 def __next__(self): 

218 return self.__iter() 

219 

220class InclusiveSubber(object): 

221 def __sub__(self, other): 

222 """suber""" 

223 copy = self.copy() 

224 copy -= other 

225 return copy 

226 

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 

236 

237 return self 

238 

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) 

244 

245 def __neg__(self): 

246 """in place negation""" 

247 for k in self: 

248 self[k] *= -1 

249 return self 

250 

251 

252class Searchable(Iterator): 

253 

254 

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 

263 

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