forked from fluentpython/example-code-2e
-
Notifications
You must be signed in to change notification settings - Fork 0
/
transformdict.py
140 lines (110 loc) · 4.35 KB
/
transformdict.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
"""Transformdict: a mapping that transforms keys on lookup
This module and ``test_transformdict.py`` were extracted from a
patch contributed to Python by Antoine Pitrou implementing his
PEP 455 -- Adding a key-transforming dictionary to collections.
That PEP was rejected, and the patch was never merged to CPython.
The original code is in ``transformdict3.patch``, part of
issue #18986: Add a case-insensitive case-preserving dict.
http://bugs.python.org/issue18986
"""
from collections.abc import MutableMapping
_sentinel = object()
class TransformDict(MutableMapping):
"""Dictionary that calls a transformation function when looking
up keys, but preserves the original keys.
>>> d = TransformDict(str.lower)
>>> d['Foo'] = 5
>>> d['foo'] == d['FOO'] == d['Foo'] == 5
True
>>> set(d.keys())
{'Foo'}
"""
__slots__ = ('_transform', '_original', '_data')
def __init__(self, transform, init_dict=None, **kwargs):
"""Create a new TransformDict with the given *transform* function.
*init_dict* and *kwargs* are optional initializers, as in the
dict constructor.
"""
if not callable(transform):
raise TypeError(
f'expected a callable, got {transform.__class__!r}')
self._transform = transform
# transformed => original
self._original = {}
self._data = {}
if init_dict:
self.update(init_dict)
if kwargs:
self.update(kwargs)
def getitem(self, key):
"""D.getitem(key) -> (stored key, value)"""
transformed = self._transform(key)
original = self._original[transformed]
value = self._data[transformed]
return original, value
@property
def transform_func(self):
"""This is TransformDict's transformation function"""
return self._transform
# Minimum set of methods required for MutableMapping
def __len__(self):
return len(self._data)
def __iter__(self):
return iter(self._original.values())
def __getitem__(self, key):
return self._data[self._transform(key)]
def __setitem__(self, key, value):
transformed = self._transform(key)
self._data[transformed] = value
self._original.setdefault(transformed, key)
def __delitem__(self, key):
transformed = self._transform(key)
del self._data[transformed]
del self._original[transformed]
# Methods overridden to mitigate the performance overhead.
def clear(self):
"""D.clear() -> None. Remove all items from D."""
self._data.clear()
self._original.clear()
def __contains__(self, key):
return self._transform(key) in self._data
def get(self, key, default=None):
"""D.get(k[,d]) -> D[k] if k in D, else d. d defaults to None."""
return self._data.get(self._transform(key), default)
def pop(self, key, default=_sentinel):
"""D.pop(k[,d]) -> v, remove key and return corresponding value.
If key is not found, d is returned if given, otherwise
KeyError is raised.
"""
transformed = self._transform(key)
if default is _sentinel:
del self._original[transformed]
return self._data.pop(transformed)
else:
self._original.pop(transformed, None)
return self._data.pop(transformed, default)
def popitem(self):
"""D.popitem() -> (k, v), remove and return some (key, value) pair
as a 2-tuple; but raise KeyError if D is empty.
"""
transformed, value = self._data.popitem()
return self._original.pop(transformed), value
# Other methods
def copy(self):
"""D.copy() -> a shallow copy of D"""
other = self.__class__(self._transform)
other._original = self._original.copy()
other._data = self._data.copy()
return other
__copy__ = copy
def __getstate__(self):
return (self._transform, self._data, self._original)
def __setstate__(self, state):
self._transform, self._data, self._original = state
def __repr__(self):
try:
equiv = dict(self)
except TypeError:
# Some keys are unhashable, fall back on .items()
equiv = list(self.items())
return f'{self.__class__.__name__}({self._transform!r}, {equiv!r})'