Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/toolz/dicttoolz.py: 27%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

105 statements  

1import operator 

2import collections 

3from functools import reduce 

4from collections.abc import Mapping 

5 

6__all__ = ('merge', 'merge_with', 'valmap', 'keymap', 'itemmap', 

7 'valfilter', 'keyfilter', 'itemfilter', 

8 'assoc', 'dissoc', 'assoc_in', 'update_in', 'get_in') 

9 

10 

11def _get_factory(f, kwargs): 

12 factory = kwargs.pop('factory', dict) 

13 if kwargs: 

14 raise TypeError("{}() got an unexpected keyword argument " 

15 "'{}'".format(f.__name__, kwargs.popitem()[0])) 

16 return factory 

17 

18 

19def merge(*dicts, **kwargs): 

20 """ Merge a collection of dictionaries 

21 

22 >>> merge({1: 'one'}, {2: 'two'}) 

23 {1: 'one', 2: 'two'} 

24 

25 Later dictionaries have precedence 

26 

27 >>> merge({1: 2, 3: 4}, {3: 3, 4: 4}) 

28 {1: 2, 3: 3, 4: 4} 

29 

30 See Also: 

31 merge_with 

32 """ 

33 if len(dicts) == 1 and not isinstance(dicts[0], Mapping): 

34 dicts = dicts[0] 

35 factory = _get_factory(merge, kwargs) 

36 

37 rv = factory() 

38 for d in dicts: 

39 rv.update(d) 

40 return rv 

41 

42 

43def merge_with(func, *dicts, **kwargs): 

44 """ Merge dictionaries and apply function to combined values 

45 

46 A key may occur in more than one dict, and all values mapped from the key 

47 will be passed to the function as a list, such as func([val1, val2, ...]). 

48 

49 >>> merge_with(sum, {1: 1, 2: 2}, {1: 10, 2: 20}) 

50 {1: 11, 2: 22} 

51 

52 >>> merge_with(first, {1: 1, 2: 2}, {2: 20, 3: 30}) # doctest: +SKIP 

53 {1: 1, 2: 2, 3: 30} 

54 

55 See Also: 

56 merge 

57 """ 

58 if len(dicts) == 1 and not isinstance(dicts[0], Mapping): 

59 dicts = dicts[0] 

60 factory = _get_factory(merge_with, kwargs) 

61 

62 values = collections.defaultdict(lambda: [].append) 

63 for d in dicts: 

64 for k, v in d.items(): 

65 values[k](v) 

66 

67 result = factory() 

68 for k, v in values.items(): 

69 result[k] = func(v.__self__) 

70 return result 

71 

72 

73def valmap(func, d, factory=dict): 

74 """ Apply function to values of dictionary 

75 

76 >>> bills = {"Alice": [20, 15, 30], "Bob": [10, 35]} 

77 >>> valmap(sum, bills) # doctest: +SKIP 

78 {'Alice': 65, 'Bob': 45} 

79 

80 See Also: 

81 keymap 

82 itemmap 

83 """ 

84 rv = factory() 

85 rv.update(zip(d.keys(), map(func, d.values()))) 

86 return rv 

87 

88 

89def keymap(func, d, factory=dict): 

90 """ Apply function to keys of dictionary 

91 

92 >>> bills = {"Alice": [20, 15, 30], "Bob": [10, 35]} 

93 >>> keymap(str.lower, bills) # doctest: +SKIP 

94 {'alice': [20, 15, 30], 'bob': [10, 35]} 

95 

96 See Also: 

97 valmap 

98 itemmap 

99 """ 

100 rv = factory() 

101 rv.update(zip(map(func, d.keys()), d.values())) 

102 return rv 

103 

104 

105def itemmap(func, d, factory=dict): 

106 """ Apply function to items of dictionary 

107 

108 >>> accountids = {"Alice": 10, "Bob": 20} 

109 >>> itemmap(reversed, accountids) # doctest: +SKIP 

110 {10: "Alice", 20: "Bob"} 

111 

112 See Also: 

113 keymap 

114 valmap 

115 """ 

116 rv = factory() 

117 rv.update(map(func, d.items())) 

118 return rv 

119 

120 

121def valfilter(predicate, d, factory=dict): 

122 """ Filter items in dictionary by value 

123 

124 >>> iseven = lambda x: x % 2 == 0 

125 >>> d = {1: 2, 2: 3, 3: 4, 4: 5} 

126 >>> valfilter(iseven, d) 

127 {1: 2, 3: 4} 

128 

129 See Also: 

130 keyfilter 

131 itemfilter 

132 valmap 

133 """ 

134 rv = factory() 

135 for k, v in d.items(): 

136 if predicate(v): 

137 rv[k] = v 

138 return rv 

139 

140 

141def keyfilter(predicate, d, factory=dict): 

142 """ Filter items in dictionary by key 

143 

144 >>> iseven = lambda x: x % 2 == 0 

145 >>> d = {1: 2, 2: 3, 3: 4, 4: 5} 

146 >>> keyfilter(iseven, d) 

147 {2: 3, 4: 5} 

148 

149 See Also: 

150 valfilter 

151 itemfilter 

152 keymap 

153 """ 

154 rv = factory() 

155 for k, v in d.items(): 

156 if predicate(k): 

157 rv[k] = v 

158 return rv 

159 

160 

161def itemfilter(predicate, d, factory=dict): 

162 """ Filter items in dictionary by item 

163 

164 >>> def isvalid(item): 

165 ... k, v = item 

166 ... return k % 2 == 0 and v < 4 

167 

168 >>> d = {1: 2, 2: 3, 3: 4, 4: 5} 

169 >>> itemfilter(isvalid, d) 

170 {2: 3} 

171 

172 See Also: 

173 keyfilter 

174 valfilter 

175 itemmap 

176 """ 

177 rv = factory() 

178 for item in d.items(): 

179 if predicate(item): 

180 k, v = item 

181 rv[k] = v 

182 return rv 

183 

184 

185def assoc(d, key, value, factory=dict): 

186 """ Return a new dict with new key value pair 

187 

188 New dict has d[key] set to value. Does not modify the initial dictionary. 

189 

190 >>> assoc({'x': 1}, 'x', 2) 

191 {'x': 2} 

192 >>> assoc({'x': 1}, 'y', 3) # doctest: +SKIP 

193 {'x': 1, 'y': 3} 

194 """ 

195 d2 = factory() 

196 d2.update(d) 

197 d2[key] = value 

198 return d2 

199 

200 

201def dissoc(d, *keys, **kwargs): 

202 """ Return a new dict with the given key(s) removed. 

203 

204 New dict has d[key] deleted for each supplied key. 

205 Does not modify the initial dictionary. 

206 

207 >>> dissoc({'x': 1, 'y': 2}, 'y') 

208 {'x': 1} 

209 >>> dissoc({'x': 1, 'y': 2}, 'y', 'x') 

210 {} 

211 >>> dissoc({'x': 1}, 'y') # Ignores missing keys 

212 {'x': 1} 

213 """ 

214 factory = _get_factory(dissoc, kwargs) 

215 d2 = factory() 

216 

217 if len(keys) < len(d) * .6: 

218 d2.update(d) 

219 for key in keys: 

220 if key in d2: 

221 del d2[key] 

222 else: 

223 remaining = set(d) 

224 remaining.difference_update(keys) 

225 for k in remaining: 

226 d2[k] = d[k] 

227 return d2 

228 

229 

230def assoc_in(d, keys, value, factory=dict): 

231 """ Return a new dict with new, potentially nested, key value pair 

232 

233 >>> purchase = {'name': 'Alice', 

234 ... 'order': {'items': ['Apple', 'Orange'], 

235 ... 'costs': [0.50, 1.25]}, 

236 ... 'credit card': '5555-1234-1234-1234'} 

237 >>> assoc_in(purchase, ['order', 'costs'], [0.25, 1.00]) # doctest: +SKIP 

238 {'credit card': '5555-1234-1234-1234', 

239 'name': 'Alice', 

240 'order': {'costs': [0.25, 1.00], 'items': ['Apple', 'Orange']}} 

241 """ 

242 return update_in(d, keys, lambda x: value, value, factory) 

243 

244 

245def update_in(d, keys, func, default=None, factory=dict): 

246 """ Update value in a (potentially) nested dictionary 

247 

248 inputs: 

249 d - dictionary on which to operate 

250 keys - list or tuple giving the location of the value to be changed in d 

251 func - function to operate on that value 

252 

253 If keys == [k0,..,kX] and d[k0]..[kX] == v, update_in returns a copy of the 

254 original dictionary with v replaced by func(v), but does not mutate the 

255 original dictionary. 

256 

257 If k0 is not a key in d, update_in creates nested dictionaries to the depth 

258 specified by the keys, with the innermost value set to func(default). 

259 

260 >>> inc = lambda x: x + 1 

261 >>> update_in({'a': 0}, ['a'], inc) 

262 {'a': 1} 

263 

264 >>> transaction = {'name': 'Alice', 

265 ... 'purchase': {'items': ['Apple', 'Orange'], 

266 ... 'costs': [0.50, 1.25]}, 

267 ... 'credit card': '5555-1234-1234-1234'} 

268 >>> update_in(transaction, ['purchase', 'costs'], sum) # doctest: +SKIP 

269 {'credit card': '5555-1234-1234-1234', 

270 'name': 'Alice', 

271 'purchase': {'costs': 1.75, 'items': ['Apple', 'Orange']}} 

272 

273 >>> # updating a value when k0 is not in d 

274 >>> update_in({}, [1, 2, 3], str, default="bar") 

275 {1: {2: {3: 'bar'}}} 

276 >>> update_in({1: 'foo'}, [2, 3, 4], inc, 0) 

277 {1: 'foo', 2: {3: {4: 1}}} 

278 """ 

279 ks = iter(keys) 

280 k = next(ks) 

281 

282 rv = inner = factory() 

283 rv.update(d) 

284 

285 for key in ks: 

286 if k in d: 

287 d = d[k] 

288 dtemp = factory() 

289 dtemp.update(d) 

290 else: 

291 d = dtemp = factory() 

292 

293 inner[k] = inner = dtemp 

294 k = key 

295 

296 if k in d: 

297 inner[k] = func(d[k]) 

298 else: 

299 inner[k] = func(default) 

300 return rv 

301 

302 

303def get_in(keys, coll, default=None, no_default=False): 

304 """ Returns coll[i0][i1]...[iX] where [i0, i1, ..., iX]==keys. 

305 

306 If coll[i0][i1]...[iX] cannot be found, returns ``default``, unless 

307 ``no_default`` is specified, then it raises KeyError or IndexError. 

308 

309 ``get_in`` is a generalization of ``operator.getitem`` for nested data 

310 structures such as dictionaries and lists. 

311 

312 >>> transaction = {'name': 'Alice', 

313 ... 'purchase': {'items': ['Apple', 'Orange'], 

314 ... 'costs': [0.50, 1.25]}, 

315 ... 'credit card': '5555-1234-1234-1234'} 

316 >>> get_in(['purchase', 'items', 0], transaction) 

317 'Apple' 

318 >>> get_in(['name'], transaction) 

319 'Alice' 

320 >>> get_in(['purchase', 'total'], transaction) 

321 >>> get_in(['purchase', 'items', 'apple'], transaction) 

322 >>> get_in(['purchase', 'items', 10], transaction) 

323 >>> get_in(['purchase', 'total'], transaction, 0) 

324 0 

325 >>> get_in(['y'], {}, no_default=True) 

326 Traceback (most recent call last): 

327 ... 

328 KeyError: 'y' 

329 

330 See Also: 

331 itertoolz.get 

332 operator.getitem 

333 """ 

334 try: 

335 return reduce(operator.getitem, keys, coll) 

336 except (KeyError, IndexError, TypeError): 

337 if no_default: 

338 raise 

339 return default