chainmap_impl.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. import sys
  2. PY3 = sys.version_info[0] >= 3
  3. if PY3:
  4. from collections.abc import MutableMapping
  5. else:
  6. from collections import MutableMapping
  7. try:
  8. from thread import get_ident
  9. except ImportError:
  10. from _thread import get_ident
  11. def recursive_repr(fillvalue='...'):
  12. 'Decorator to make a repr function return fillvalue for a recursive call'
  13. def decorating_function(user_function):
  14. repr_running = set()
  15. def wrapper(self):
  16. key = id(self), get_ident()
  17. if key in repr_running:
  18. return fillvalue
  19. repr_running.add(key)
  20. try:
  21. result = user_function(self)
  22. finally:
  23. repr_running.discard(key)
  24. return result
  25. # Can't use functools.wraps() here because of bootstrap issues
  26. wrapper.__module__ = getattr(user_function, '__module__')
  27. wrapper.__doc__ = getattr(user_function, '__doc__')
  28. wrapper.__name__ = getattr(user_function, '__name__')
  29. return wrapper
  30. return decorating_function
  31. class ChainMap(MutableMapping):
  32. """ A ChainMap groups multiple dicts (or other mappings) together
  33. to create a single, updatable view.
  34. The underlying mappings are stored in a list. That list is public and can
  35. be accessed / updated using the *maps* attribute. There is no other state.
  36. Lookups search the underlying mappings successively until a key is found.
  37. In contrast, writes, updates, and deletions only operate on the first
  38. mapping.
  39. """
  40. def __init__(self, *maps):
  41. """Initialize a ChainMap by setting *maps* to the given mappings.
  42. If no mappings are provided, a single empty dictionary is used.
  43. """
  44. self.maps = list(maps) or [{}] # always at least one map
  45. def __missing__(self, key):
  46. raise KeyError(key)
  47. def __getitem__(self, key):
  48. for mapping in self.maps:
  49. try:
  50. # can't use 'key in mapping' with defaultdict
  51. return mapping[key]
  52. except KeyError:
  53. pass
  54. # support subclasses that define __missing__
  55. return self.__missing__(key)
  56. def get(self, key, default=None):
  57. return self[key] if key in self else default
  58. def __len__(self):
  59. # reuses stored hash values if possible
  60. return len(set().union(*self.maps))
  61. def __iter__(self):
  62. return iter(set().union(*self.maps))
  63. def __contains__(self, key):
  64. return any(key in m for m in self.maps)
  65. def __bool__(self):
  66. return any(self.maps)
  67. @recursive_repr()
  68. def __repr__(self):
  69. return '{0.__class__.__name__}({1})'.format(
  70. self, ', '.join(repr(m) for m in self.maps))
  71. @classmethod
  72. def fromkeys(cls, iterable, *args):
  73. 'Create a ChainMap with a single dict created from the iterable.'
  74. return cls(dict.fromkeys(iterable, *args))
  75. def copy(self):
  76. """
  77. New ChainMap or subclass with a new copy of maps[0] and refs to
  78. maps[1:]
  79. """
  80. return self.__class__(self.maps[0].copy(), *self.maps[1:])
  81. __copy__ = copy
  82. def new_child(self, m=None): # like Django's Context.push()
  83. """
  84. New ChainMap with a new map followed by all previous maps. If no
  85. map is provided, an empty dict is used.
  86. """
  87. if m is None:
  88. m = {}
  89. return self.__class__(m, *self.maps)
  90. @property
  91. def parents(self): # like Django's Context.pop()
  92. 'New ChainMap from maps[1:].'
  93. return self.__class__(*self.maps[1:])
  94. def __setitem__(self, key, value):
  95. self.maps[0][key] = value
  96. def __delitem__(self, key):
  97. try:
  98. del self.maps[0][key]
  99. except KeyError:
  100. raise KeyError('Key not found in the first mapping: {!r}'
  101. .format(key))
  102. def popitem(self):
  103. """
  104. Remove and return an item pair from maps[0]. Raise KeyError is maps[0]
  105. is empty.
  106. """
  107. try:
  108. return self.maps[0].popitem()
  109. except KeyError:
  110. raise KeyError('No keys found in the first mapping.')
  111. def pop(self, key, *args):
  112. """
  113. Remove *key* from maps[0] and return its value. Raise KeyError if
  114. *key* not in maps[0].
  115. """
  116. try:
  117. return self.maps[0].pop(key, *args)
  118. except KeyError:
  119. raise KeyError('Key not found in the first mapping: {!r}'
  120. .format(key))
  121. def clear(self):
  122. 'Clear maps[0], leaving maps[1:] intact.'
  123. self.maps[0].clear()