__init__.py 26 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091
  1. from __future__ import annotations
  2. import collections.abc
  3. import copy
  4. import functools
  5. import itertools
  6. import operator
  7. import random
  8. import re
  9. from collections.abc import Container, Iterable, Mapping
  10. from typing import TYPE_CHECKING, Any, Callable, Dict, TypeVar, Union, overload
  11. import jaraco.text
  12. if TYPE_CHECKING:
  13. from _operator import _SupportsComparison
  14. from _typeshed import SupportsKeysAndGetItem
  15. from typing_extensions import Self
  16. _RangeMapKT = TypeVar('_RangeMapKT', bound=_SupportsComparison)
  17. else:
  18. # _SupportsComparison doesn't exist at runtime,
  19. # but _RangeMapKT is used in RangeMap's superclass' type parameters
  20. _RangeMapKT = TypeVar('_RangeMapKT')
  21. _T = TypeVar('_T')
  22. _VT = TypeVar('_VT')
  23. _Matchable = Union[Callable, Container, Iterable, re.Pattern]
  24. def _dispatch(obj: _Matchable) -> Callable:
  25. # can't rely on singledispatch for Union[Container, Iterable]
  26. # due to ambiguity
  27. # (https://peps.python.org/pep-0443/#abstract-base-classes).
  28. if isinstance(obj, re.Pattern):
  29. return obj.fullmatch
  30. # mypy issue: https://github.com/python/mypy/issues/11071
  31. if not isinstance(obj, Callable): # type: ignore[arg-type]
  32. if not isinstance(obj, Container):
  33. obj = set(obj) # type: ignore[arg-type]
  34. obj = obj.__contains__
  35. return obj # type: ignore[return-value]
  36. class Projection(collections.abc.Mapping):
  37. """
  38. Project a set of keys over a mapping
  39. >>> sample = {'a': 1, 'b': 2, 'c': 3}
  40. >>> prj = Projection(['a', 'c', 'd'], sample)
  41. >>> dict(prj)
  42. {'a': 1, 'c': 3}
  43. Projection also accepts an iterable or callable or pattern.
  44. >>> iter_prj = Projection(iter('acd'), sample)
  45. >>> call_prj = Projection(lambda k: ord(k) in (97, 99, 100), sample)
  46. >>> pat_prj = Projection(re.compile(r'[acd]'), sample)
  47. >>> prj == iter_prj == call_prj == pat_prj
  48. True
  49. Keys should only appear if they were specified and exist in the space.
  50. Order is retained.
  51. >>> list(prj)
  52. ['a', 'c']
  53. Attempting to access a key not in the projection
  54. results in a KeyError.
  55. >>> prj['b']
  56. Traceback (most recent call last):
  57. ...
  58. KeyError: 'b'
  59. Use the projection to update another dict.
  60. >>> target = {'a': 2, 'b': 2}
  61. >>> target.update(prj)
  62. >>> target
  63. {'a': 1, 'b': 2, 'c': 3}
  64. Projection keeps a reference to the original dict, so
  65. modifying the original dict may modify the Projection.
  66. >>> del sample['a']
  67. >>> dict(prj)
  68. {'c': 3}
  69. """
  70. def __init__(self, keys: _Matchable, space: Mapping):
  71. self._match = _dispatch(keys)
  72. self._space = space
  73. def __getitem__(self, key):
  74. if not self._match(key):
  75. raise KeyError(key)
  76. return self._space[key]
  77. def _keys_resolved(self):
  78. return filter(self._match, self._space)
  79. def __iter__(self):
  80. return self._keys_resolved()
  81. def __len__(self):
  82. return len(tuple(self._keys_resolved()))
  83. class Mask(Projection):
  84. """
  85. The inverse of a :class:`Projection`, masking out keys.
  86. >>> sample = {'a': 1, 'b': 2, 'c': 3}
  87. >>> msk = Mask(['a', 'c', 'd'], sample)
  88. >>> dict(msk)
  89. {'b': 2}
  90. """
  91. def __init__(self, *args, **kwargs):
  92. super().__init__(*args, **kwargs)
  93. # self._match = compose(operator.not_, self._match)
  94. self._match = lambda key, orig=self._match: not orig(key)
  95. def dict_map(function, dictionary):
  96. """
  97. Return a new dict with function applied to values of dictionary.
  98. >>> dict_map(lambda x: x+1, dict(a=1, b=2))
  99. {'a': 2, 'b': 3}
  100. """
  101. return dict((key, function(value)) for key, value in dictionary.items())
  102. class RangeMap(Dict[_RangeMapKT, _VT]):
  103. """
  104. A dictionary-like object that uses the keys as bounds for a range.
  105. Inclusion of the value for that range is determined by the
  106. key_match_comparator, which defaults to less-than-or-equal.
  107. A value is returned for a key if it is the first key that matches in
  108. the sorted list of keys.
  109. One may supply keyword parameters to be passed to the sort function used
  110. to sort keys (i.e. key, reverse) as sort_params.
  111. Create a map that maps 1-3 -> 'a', 4-6 -> 'b'
  112. >>> r = RangeMap({3: 'a', 6: 'b'}) # boy, that was easy
  113. >>> r[1], r[2], r[3], r[4], r[5], r[6]
  114. ('a', 'a', 'a', 'b', 'b', 'b')
  115. Even float values should work so long as the comparison operator
  116. supports it.
  117. >>> r[4.5]
  118. 'b'
  119. Notice that the way rangemap is defined, it must be open-ended
  120. on one side.
  121. >>> r[0]
  122. 'a'
  123. >>> r[-1]
  124. 'a'
  125. One can close the open-end of the RangeMap by using undefined_value
  126. >>> r = RangeMap({0: RangeMap.undefined_value, 3: 'a', 6: 'b'})
  127. >>> r[0]
  128. Traceback (most recent call last):
  129. ...
  130. KeyError: 0
  131. One can get the first or last elements in the range by using RangeMap.Item
  132. >>> last_item = RangeMap.Item(-1)
  133. >>> r[last_item]
  134. 'b'
  135. .last_item is a shortcut for Item(-1)
  136. >>> r[RangeMap.last_item]
  137. 'b'
  138. Sometimes it's useful to find the bounds for a RangeMap
  139. >>> r.bounds()
  140. (0, 6)
  141. RangeMap supports .get(key, default)
  142. >>> r.get(0, 'not found')
  143. 'not found'
  144. >>> r.get(7, 'not found')
  145. 'not found'
  146. One often wishes to define the ranges by their left-most values,
  147. which requires use of sort params and a key_match_comparator.
  148. >>> r = RangeMap({1: 'a', 4: 'b'},
  149. ... sort_params=dict(reverse=True),
  150. ... key_match_comparator=operator.ge)
  151. >>> r[1], r[2], r[3], r[4], r[5], r[6]
  152. ('a', 'a', 'a', 'b', 'b', 'b')
  153. That wasn't nearly as easy as before, so an alternate constructor
  154. is provided:
  155. >>> r = RangeMap.left({1: 'a', 4: 'b', 7: RangeMap.undefined_value})
  156. >>> r[1], r[2], r[3], r[4], r[5], r[6]
  157. ('a', 'a', 'a', 'b', 'b', 'b')
  158. """
  159. def __init__(
  160. self,
  161. source: (
  162. SupportsKeysAndGetItem[_RangeMapKT, _VT] | Iterable[tuple[_RangeMapKT, _VT]]
  163. ),
  164. sort_params: Mapping[str, Any] = {},
  165. key_match_comparator: Callable[[_RangeMapKT, _RangeMapKT], bool] = operator.le,
  166. ):
  167. dict.__init__(self, source)
  168. self.sort_params = sort_params
  169. self.match = key_match_comparator
  170. @classmethod
  171. def left(
  172. cls,
  173. source: (
  174. SupportsKeysAndGetItem[_RangeMapKT, _VT] | Iterable[tuple[_RangeMapKT, _VT]]
  175. ),
  176. ) -> Self:
  177. return cls(
  178. source, sort_params=dict(reverse=True), key_match_comparator=operator.ge
  179. )
  180. def __getitem__(self, item: _RangeMapKT) -> _VT:
  181. sorted_keys = sorted(self.keys(), **self.sort_params)
  182. if isinstance(item, RangeMap.Item):
  183. result = self.__getitem__(sorted_keys[item])
  184. else:
  185. key = self._find_first_match_(sorted_keys, item)
  186. result = dict.__getitem__(self, key)
  187. if result is RangeMap.undefined_value:
  188. raise KeyError(key)
  189. return result
  190. @overload # type: ignore[override] # Signature simplified over dict and Mapping
  191. def get(self, key: _RangeMapKT, default: _T) -> _VT | _T: ...
  192. @overload
  193. def get(self, key: _RangeMapKT, default: None = None) -> _VT | None: ...
  194. def get(self, key: _RangeMapKT, default: _T | None = None) -> _VT | _T | None:
  195. """
  196. Return the value for key if key is in the dictionary, else default.
  197. If default is not given, it defaults to None, so that this method
  198. never raises a KeyError.
  199. """
  200. try:
  201. return self[key]
  202. except KeyError:
  203. return default
  204. def _find_first_match_(
  205. self, keys: Iterable[_RangeMapKT], item: _RangeMapKT
  206. ) -> _RangeMapKT:
  207. is_match = functools.partial(self.match, item)
  208. matches = filter(is_match, keys)
  209. try:
  210. return next(matches)
  211. except StopIteration:
  212. raise KeyError(item) from None
  213. def bounds(self) -> tuple[_RangeMapKT, _RangeMapKT]:
  214. sorted_keys = sorted(self.keys(), **self.sort_params)
  215. return (sorted_keys[RangeMap.first_item], sorted_keys[RangeMap.last_item])
  216. # some special values for the RangeMap
  217. undefined_value = type('RangeValueUndefined', (), {})()
  218. class Item(int):
  219. """RangeMap Item"""
  220. first_item = Item(0)
  221. last_item = Item(-1)
  222. def __identity(x):
  223. return x
  224. def sorted_items(d, key=__identity, reverse=False):
  225. """
  226. Return the items of the dictionary sorted by the keys.
  227. >>> sample = dict(foo=20, bar=42, baz=10)
  228. >>> tuple(sorted_items(sample))
  229. (('bar', 42), ('baz', 10), ('foo', 20))
  230. >>> reverse_string = lambda s: ''.join(reversed(s))
  231. >>> tuple(sorted_items(sample, key=reverse_string))
  232. (('foo', 20), ('bar', 42), ('baz', 10))
  233. >>> tuple(sorted_items(sample, reverse=True))
  234. (('foo', 20), ('baz', 10), ('bar', 42))
  235. """
  236. # wrap the key func so it operates on the first element of each item
  237. def pairkey_key(item):
  238. return key(item[0])
  239. return sorted(d.items(), key=pairkey_key, reverse=reverse)
  240. class KeyTransformingDict(dict):
  241. """
  242. A dict subclass that transforms the keys before they're used.
  243. Subclasses may override the default transform_key to customize behavior.
  244. """
  245. @staticmethod
  246. def transform_key(key): # pragma: nocover
  247. return key
  248. def __init__(self, *args, **kargs):
  249. super().__init__()
  250. # build a dictionary using the default constructs
  251. d = dict(*args, **kargs)
  252. # build this dictionary using transformed keys.
  253. for item in d.items():
  254. self.__setitem__(*item)
  255. def __setitem__(self, key, val):
  256. key = self.transform_key(key)
  257. super().__setitem__(key, val)
  258. def __getitem__(self, key):
  259. key = self.transform_key(key)
  260. return super().__getitem__(key)
  261. def __contains__(self, key):
  262. key = self.transform_key(key)
  263. return super().__contains__(key)
  264. def __delitem__(self, key):
  265. key = self.transform_key(key)
  266. return super().__delitem__(key)
  267. def get(self, key, *args, **kwargs):
  268. key = self.transform_key(key)
  269. return super().get(key, *args, **kwargs)
  270. def setdefault(self, key, *args, **kwargs):
  271. key = self.transform_key(key)
  272. return super().setdefault(key, *args, **kwargs)
  273. def pop(self, key, *args, **kwargs):
  274. key = self.transform_key(key)
  275. return super().pop(key, *args, **kwargs)
  276. def matching_key_for(self, key):
  277. """
  278. Given a key, return the actual key stored in self that matches.
  279. Raise KeyError if the key isn't found.
  280. """
  281. try:
  282. return next(e_key for e_key in self.keys() if e_key == key)
  283. except StopIteration as err:
  284. raise KeyError(key) from err
  285. class FoldedCaseKeyedDict(KeyTransformingDict):
  286. """
  287. A case-insensitive dictionary (keys are compared as insensitive
  288. if they are strings).
  289. >>> d = FoldedCaseKeyedDict()
  290. >>> d['heLlo'] = 'world'
  291. >>> list(d.keys()) == ['heLlo']
  292. True
  293. >>> list(d.values()) == ['world']
  294. True
  295. >>> d['hello'] == 'world'
  296. True
  297. >>> 'hello' in d
  298. True
  299. >>> 'HELLO' in d
  300. True
  301. >>> print(repr(FoldedCaseKeyedDict({'heLlo': 'world'})))
  302. {'heLlo': 'world'}
  303. >>> d = FoldedCaseKeyedDict({'heLlo': 'world'})
  304. >>> print(d['hello'])
  305. world
  306. >>> print(d['Hello'])
  307. world
  308. >>> list(d.keys())
  309. ['heLlo']
  310. >>> d = FoldedCaseKeyedDict({'heLlo': 'world', 'Hello': 'world'})
  311. >>> list(d.values())
  312. ['world']
  313. >>> key, = d.keys()
  314. >>> key in ['heLlo', 'Hello']
  315. True
  316. >>> del d['HELLO']
  317. >>> d
  318. {}
  319. get should work
  320. >>> d['Sumthin'] = 'else'
  321. >>> d.get('SUMTHIN')
  322. 'else'
  323. >>> d.get('OTHER', 'thing')
  324. 'thing'
  325. >>> del d['sumthin']
  326. setdefault should also work
  327. >>> d['This'] = 'that'
  328. >>> print(d.setdefault('this', 'other'))
  329. that
  330. >>> len(d)
  331. 1
  332. >>> print(d['this'])
  333. that
  334. >>> print(d.setdefault('That', 'other'))
  335. other
  336. >>> print(d['THAT'])
  337. other
  338. Make it pop!
  339. >>> print(d.pop('THAT'))
  340. other
  341. To retrieve the key in its originally-supplied form, use matching_key_for
  342. >>> print(d.matching_key_for('this'))
  343. This
  344. >>> d.matching_key_for('missing')
  345. Traceback (most recent call last):
  346. ...
  347. KeyError: 'missing'
  348. """
  349. @staticmethod
  350. def transform_key(key):
  351. return jaraco.text.FoldedCase(key)
  352. class DictAdapter:
  353. """
  354. Provide a getitem interface for attributes of an object.
  355. Let's say you want to get at the string.lowercase property in a formatted
  356. string. It's easy with DictAdapter.
  357. >>> import string
  358. >>> print("lowercase is %(ascii_lowercase)s" % DictAdapter(string))
  359. lowercase is abcdefghijklmnopqrstuvwxyz
  360. """
  361. def __init__(self, wrapped_ob):
  362. self.object = wrapped_ob
  363. def __getitem__(self, name):
  364. return getattr(self.object, name)
  365. class ItemsAsAttributes:
  366. """
  367. Mix-in class to enable a mapping object to provide items as
  368. attributes.
  369. >>> C = type('C', (dict, ItemsAsAttributes), dict())
  370. >>> i = C()
  371. >>> i['foo'] = 'bar'
  372. >>> i.foo
  373. 'bar'
  374. Natural attribute access takes precedence
  375. >>> i.foo = 'henry'
  376. >>> i.foo
  377. 'henry'
  378. But as you might expect, the mapping functionality is preserved.
  379. >>> i['foo']
  380. 'bar'
  381. A normal attribute error should be raised if an attribute is
  382. requested that doesn't exist.
  383. >>> i.missing
  384. Traceback (most recent call last):
  385. ...
  386. AttributeError: 'C' object has no attribute 'missing'
  387. It also works on dicts that customize __getitem__
  388. >>> missing_func = lambda self, key: 'missing item'
  389. >>> C = type(
  390. ... 'C',
  391. ... (dict, ItemsAsAttributes),
  392. ... dict(__missing__ = missing_func),
  393. ... )
  394. >>> i = C()
  395. >>> i.missing
  396. 'missing item'
  397. >>> i.foo
  398. 'missing item'
  399. """
  400. def __getattr__(self, key):
  401. try:
  402. return getattr(super(), key)
  403. except AttributeError as e:
  404. # attempt to get the value from the mapping (return self[key])
  405. # but be careful not to lose the original exception context.
  406. noval = object()
  407. def _safe_getitem(cont, key, missing_result):
  408. try:
  409. return cont[key]
  410. except KeyError:
  411. return missing_result
  412. result = _safe_getitem(self, key, noval)
  413. if result is not noval:
  414. return result
  415. # raise the original exception, but use the original class
  416. # name, not 'super'.
  417. (message,) = e.args
  418. message = message.replace('super', self.__class__.__name__, 1)
  419. e.args = (message,)
  420. raise
  421. def invert_map(map):
  422. """
  423. Given a dictionary, return another dictionary with keys and values
  424. switched. If any of the values resolve to the same key, raises
  425. a ValueError.
  426. >>> numbers = dict(a=1, b=2, c=3)
  427. >>> letters = invert_map(numbers)
  428. >>> letters[1]
  429. 'a'
  430. >>> numbers['d'] = 3
  431. >>> invert_map(numbers)
  432. Traceback (most recent call last):
  433. ...
  434. ValueError: Key conflict in inverted mapping
  435. """
  436. res = dict((v, k) for k, v in map.items())
  437. if not len(res) == len(map):
  438. raise ValueError('Key conflict in inverted mapping')
  439. return res
  440. class IdentityOverrideMap(dict):
  441. """
  442. A dictionary that by default maps each key to itself, but otherwise
  443. acts like a normal dictionary.
  444. >>> d = IdentityOverrideMap()
  445. >>> d[42]
  446. 42
  447. >>> d['speed'] = 'speedo'
  448. >>> print(d['speed'])
  449. speedo
  450. """
  451. def __missing__(self, key):
  452. return key
  453. class DictStack(list, collections.abc.MutableMapping):
  454. """
  455. A stack of dictionaries that behaves as a view on those dictionaries,
  456. giving preference to the last.
  457. >>> stack = DictStack([dict(a=1, c=2), dict(b=2, a=2)])
  458. >>> stack['a']
  459. 2
  460. >>> stack['b']
  461. 2
  462. >>> stack['c']
  463. 2
  464. >>> len(stack)
  465. 3
  466. >>> stack.push(dict(a=3))
  467. >>> stack['a']
  468. 3
  469. >>> stack['a'] = 4
  470. >>> set(stack.keys()) == set(['a', 'b', 'c'])
  471. True
  472. >>> set(stack.items()) == set([('a', 4), ('b', 2), ('c', 2)])
  473. True
  474. >>> dict(**stack) == dict(stack) == dict(a=4, c=2, b=2)
  475. True
  476. >>> d = stack.pop()
  477. >>> stack['a']
  478. 2
  479. >>> d = stack.pop()
  480. >>> stack['a']
  481. 1
  482. >>> stack.get('b', None)
  483. >>> 'c' in stack
  484. True
  485. >>> del stack['c']
  486. >>> dict(stack)
  487. {'a': 1}
  488. """
  489. def __iter__(self):
  490. dicts = list.__iter__(self)
  491. return iter(set(itertools.chain.from_iterable(c.keys() for c in dicts)))
  492. def __getitem__(self, key):
  493. for scope in reversed(tuple(list.__iter__(self))):
  494. if key in scope:
  495. return scope[key]
  496. raise KeyError(key)
  497. push = list.append
  498. def __contains__(self, other):
  499. return collections.abc.Mapping.__contains__(self, other)
  500. def __len__(self):
  501. return len(list(iter(self)))
  502. def __setitem__(self, key, item):
  503. last = list.__getitem__(self, -1)
  504. return last.__setitem__(key, item)
  505. def __delitem__(self, key):
  506. last = list.__getitem__(self, -1)
  507. return last.__delitem__(key)
  508. # workaround for mypy confusion
  509. def pop(self, *args, **kwargs):
  510. return list.pop(self, *args, **kwargs)
  511. class BijectiveMap(dict):
  512. """
  513. A Bijective Map (two-way mapping).
  514. Implemented as a simple dictionary of 2x the size, mapping values back
  515. to keys.
  516. Note, this implementation may be incomplete. If there's not a test for
  517. your use case below, it's likely to fail, so please test and send pull
  518. requests or patches for additional functionality needed.
  519. >>> m = BijectiveMap()
  520. >>> m['a'] = 'b'
  521. >>> m == {'a': 'b', 'b': 'a'}
  522. True
  523. >>> print(m['b'])
  524. a
  525. >>> m['c'] = 'd'
  526. >>> len(m)
  527. 2
  528. Some weird things happen if you map an item to itself or overwrite a
  529. single key of a pair, so it's disallowed.
  530. >>> m['e'] = 'e'
  531. Traceback (most recent call last):
  532. ValueError: Key cannot map to itself
  533. >>> m['d'] = 'e'
  534. Traceback (most recent call last):
  535. ValueError: Key/Value pairs may not overlap
  536. >>> m['e'] = 'd'
  537. Traceback (most recent call last):
  538. ValueError: Key/Value pairs may not overlap
  539. >>> print(m.pop('d'))
  540. c
  541. >>> 'c' in m
  542. False
  543. >>> m = BijectiveMap(dict(a='b'))
  544. >>> len(m)
  545. 1
  546. >>> print(m['b'])
  547. a
  548. >>> m = BijectiveMap()
  549. >>> m.update(a='b')
  550. >>> m['b']
  551. 'a'
  552. >>> del m['b']
  553. >>> len(m)
  554. 0
  555. >>> 'a' in m
  556. False
  557. """
  558. def __init__(self, *args, **kwargs):
  559. super().__init__()
  560. self.update(*args, **kwargs)
  561. def __setitem__(self, item, value):
  562. if item == value:
  563. raise ValueError("Key cannot map to itself")
  564. overlap = (
  565. item in self
  566. and self[item] != value
  567. or value in self
  568. and self[value] != item
  569. )
  570. if overlap:
  571. raise ValueError("Key/Value pairs may not overlap")
  572. super().__setitem__(item, value)
  573. super().__setitem__(value, item)
  574. def __delitem__(self, item):
  575. self.pop(item)
  576. def __len__(self):
  577. return super().__len__() // 2
  578. def pop(self, key, *args, **kwargs):
  579. mirror = self[key]
  580. super().__delitem__(mirror)
  581. return super().pop(key, *args, **kwargs)
  582. def update(self, *args, **kwargs):
  583. # build a dictionary using the default constructs
  584. d = dict(*args, **kwargs)
  585. # build this dictionary using transformed keys.
  586. for item in d.items():
  587. self.__setitem__(*item)
  588. class FrozenDict(collections.abc.Mapping, collections.abc.Hashable):
  589. """
  590. An immutable mapping.
  591. >>> a = FrozenDict(a=1, b=2)
  592. >>> b = FrozenDict(a=1, b=2)
  593. >>> a == b
  594. True
  595. >>> a == dict(a=1, b=2)
  596. True
  597. >>> dict(a=1, b=2) == a
  598. True
  599. >>> 'a' in a
  600. True
  601. >>> type(hash(a)) is type(0)
  602. True
  603. >>> set(iter(a)) == {'a', 'b'}
  604. True
  605. >>> len(a)
  606. 2
  607. >>> a['a'] == a.get('a') == 1
  608. True
  609. >>> a['c'] = 3
  610. Traceback (most recent call last):
  611. ...
  612. TypeError: 'FrozenDict' object does not support item assignment
  613. >>> a.update(y=3)
  614. Traceback (most recent call last):
  615. ...
  616. AttributeError: 'FrozenDict' object has no attribute 'update'
  617. Copies should compare equal
  618. >>> copy.copy(a) == a
  619. True
  620. Copies should be the same type
  621. >>> isinstance(copy.copy(a), FrozenDict)
  622. True
  623. FrozenDict supplies .copy(), even though
  624. collections.abc.Mapping doesn't demand it.
  625. >>> a.copy() == a
  626. True
  627. >>> a.copy() is not a
  628. True
  629. """
  630. __slots__ = ['__data']
  631. def __new__(cls, *args, **kwargs):
  632. self = super().__new__(cls)
  633. self.__data = dict(*args, **kwargs)
  634. return self
  635. # Container
  636. def __contains__(self, key):
  637. return key in self.__data
  638. # Hashable
  639. def __hash__(self):
  640. return hash(tuple(sorted(self.__data.items())))
  641. # Mapping
  642. def __iter__(self):
  643. return iter(self.__data)
  644. def __len__(self):
  645. return len(self.__data)
  646. def __getitem__(self, key):
  647. return self.__data[key]
  648. # override get for efficiency provided by dict
  649. def get(self, *args, **kwargs):
  650. return self.__data.get(*args, **kwargs)
  651. # override eq to recognize underlying implementation
  652. def __eq__(self, other):
  653. if isinstance(other, FrozenDict):
  654. other = other.__data
  655. return self.__data.__eq__(other)
  656. def copy(self):
  657. "Return a shallow copy of self"
  658. return copy.copy(self)
  659. class Enumeration(ItemsAsAttributes, BijectiveMap):
  660. """
  661. A convenient way to provide enumerated values
  662. >>> e = Enumeration('a b c')
  663. >>> e['a']
  664. 0
  665. >>> e.a
  666. 0
  667. >>> e[1]
  668. 'b'
  669. >>> set(e.names) == set('abc')
  670. True
  671. >>> set(e.codes) == set(range(3))
  672. True
  673. >>> e.get('d') is None
  674. True
  675. Codes need not start with 0
  676. >>> e = Enumeration('a b c', range(1, 4))
  677. >>> e['a']
  678. 1
  679. >>> e[3]
  680. 'c'
  681. """
  682. def __init__(self, names, codes=None):
  683. if isinstance(names, str):
  684. names = names.split()
  685. if codes is None:
  686. codes = itertools.count()
  687. super().__init__(zip(names, codes))
  688. @property
  689. def names(self):
  690. return (key for key in self if isinstance(key, str))
  691. @property
  692. def codes(self):
  693. return (self[name] for name in self.names)
  694. class Everything:
  695. """
  696. A collection "containing" every possible thing.
  697. >>> 'foo' in Everything()
  698. True
  699. >>> import random
  700. >>> random.randint(1, 999) in Everything()
  701. True
  702. >>> random.choice([None, 'foo', 42, ('a', 'b', 'c')]) in Everything()
  703. True
  704. """
  705. def __contains__(self, other):
  706. return True
  707. class InstrumentedDict(collections.UserDict):
  708. """
  709. Instrument an existing dictionary with additional
  710. functionality, but always reference and mutate
  711. the original dictionary.
  712. >>> orig = {'a': 1, 'b': 2}
  713. >>> inst = InstrumentedDict(orig)
  714. >>> inst['a']
  715. 1
  716. >>> inst['c'] = 3
  717. >>> orig['c']
  718. 3
  719. >>> inst.keys() == orig.keys()
  720. True
  721. """
  722. def __init__(self, data):
  723. super().__init__()
  724. self.data = data
  725. class Least:
  726. """
  727. A value that is always lesser than any other
  728. >>> least = Least()
  729. >>> 3 < least
  730. False
  731. >>> 3 > least
  732. True
  733. >>> least < 3
  734. True
  735. >>> least <= 3
  736. True
  737. >>> least > 3
  738. False
  739. >>> 'x' > least
  740. True
  741. >>> None > least
  742. True
  743. """
  744. def __le__(self, other):
  745. return True
  746. __lt__ = __le__
  747. def __ge__(self, other):
  748. return False
  749. __gt__ = __ge__
  750. class Greatest:
  751. """
  752. A value that is always greater than any other
  753. >>> greatest = Greatest()
  754. >>> 3 < greatest
  755. True
  756. >>> 3 > greatest
  757. False
  758. >>> greatest < 3
  759. False
  760. >>> greatest > 3
  761. True
  762. >>> greatest >= 3
  763. True
  764. >>> 'x' > greatest
  765. False
  766. >>> None > greatest
  767. False
  768. """
  769. def __ge__(self, other):
  770. return True
  771. __gt__ = __ge__
  772. def __le__(self, other):
  773. return False
  774. __lt__ = __le__
  775. def pop_all(items):
  776. """
  777. Clear items in place and return a copy of items.
  778. >>> items = [1, 2, 3]
  779. >>> popped = pop_all(items)
  780. >>> popped is items
  781. False
  782. >>> popped
  783. [1, 2, 3]
  784. >>> items
  785. []
  786. """
  787. result, items[:] = items[:], []
  788. return result
  789. class FreezableDefaultDict(collections.defaultdict):
  790. """
  791. Often it is desirable to prevent the mutation of
  792. a default dict after its initial construction, such
  793. as to prevent mutation during iteration.
  794. >>> dd = FreezableDefaultDict(list)
  795. >>> dd[0].append('1')
  796. >>> dd.freeze()
  797. >>> dd[1]
  798. []
  799. >>> len(dd)
  800. 1
  801. """
  802. def __missing__(self, key):
  803. return getattr(self, '_frozen', super().__missing__)(key)
  804. def freeze(self):
  805. self._frozen = lambda key: self.default_factory()
  806. class Accumulator:
  807. def __init__(self, initial=0):
  808. self.val = initial
  809. def __call__(self, val):
  810. self.val += val
  811. return self.val
  812. class WeightedLookup(RangeMap):
  813. """
  814. Given parameters suitable for a dict representing keys
  815. and a weighted proportion, return a RangeMap representing
  816. spans of values proportial to the weights:
  817. >>> even = WeightedLookup(a=1, b=1)
  818. [0, 1) -> a
  819. [1, 2) -> b
  820. >>> lk = WeightedLookup(a=1, b=2)
  821. [0, 1) -> a
  822. [1, 3) -> b
  823. >>> lk[.5]
  824. 'a'
  825. >>> lk[1.5]
  826. 'b'
  827. Adds ``.random()`` to select a random weighted value:
  828. >>> lk.random() in ['a', 'b']
  829. True
  830. >>> choices = [lk.random() for x in range(1000)]
  831. Statistically speaking, choices should be .5 a:b
  832. >>> ratio = choices.count('a') / choices.count('b')
  833. >>> .4 < ratio < .6
  834. True
  835. """
  836. def __init__(self, *args, **kwargs):
  837. raw = dict(*args, **kwargs)
  838. # allocate keys by weight
  839. indexes = map(Accumulator(), raw.values())
  840. super().__init__(zip(indexes, raw.keys()), key_match_comparator=operator.lt)
  841. def random(self):
  842. lower, upper = self.bounds()
  843. selector = random.random() * upper
  844. return self[selector]