Skip to content

Commit a63cad5

Browse files
author
Marco Sulla
committed
give to caesar...
1 parent 3208ed8 commit a63cad5

1 file changed

Lines changed: 248 additions & 0 deletions

File tree

src/frozendict/_frozendict_py.py

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
from copy import deepcopy
2+
3+
4+
def immutable(self, *args, **kwargs):
5+
r"""
6+
Function for not implemented method since the object is immutable
7+
"""
8+
9+
raise AttributeError(
10+
f"'{self.__class__.__name__}' object is read-only"
11+
)
12+
13+
14+
_empty_frozendict = None
15+
16+
17+
class frozendict(dict):
18+
r"""
19+
A simple immutable dictionary.
20+
21+
The API is the same as `dict`, without methods that can change the
22+
immutability. In addition, it supports __hash__().
23+
"""
24+
25+
__slots__ = (
26+
"_hash",
27+
)
28+
29+
@classmethod
30+
def fromkeys(cls, *args, **kwargs):
31+
r"""
32+
Identical to dict.fromkeys().
33+
"""
34+
35+
return cls(dict.fromkeys(*args, **kwargs))
36+
37+
def __new__(e4b37cdf_d78a_4632_bade_6f0579d8efac, *args, **kwargs):
38+
cls = e4b37cdf_d78a_4632_bade_6f0579d8efac
39+
40+
has_kwargs = bool(kwargs)
41+
continue_creation = True
42+
43+
# check if there's only an argument and it's of the same class
44+
if len(args) == 1 and not has_kwargs:
45+
it = args[0]
46+
47+
# no isinstance, to avoid subclassing problems
48+
if it.__class__ == frozendict and cls == frozendict:
49+
self = it
50+
continue_creation = False
51+
52+
if continue_creation:
53+
self = dict.__new__(cls, *args, **kwargs)
54+
55+
dict.__init__(self, *args, **kwargs)
56+
57+
# empty singleton - start
58+
59+
if self.__class__ == frozendict and not len(self):
60+
global _empty_frozendict
61+
62+
if _empty_frozendict is None:
63+
_empty_frozendict = self
64+
else:
65+
self = _empty_frozendict
66+
continue_creation = False
67+
68+
# empty singleton - end
69+
70+
if continue_creation:
71+
object.__setattr__(self, "_hash", -1)
72+
73+
return self
74+
75+
def __init__(self, *args, **kwargs):
76+
pass
77+
78+
def __hash__(self, *args, **kwargs):
79+
r"""
80+
Calculates the hash if all values are hashable, otherwise
81+
raises a TypeError.
82+
"""
83+
84+
if self._hash != -1:
85+
_hash = self._hash
86+
else:
87+
fs = frozenset(self.items())
88+
_hash = hash(fs)
89+
90+
object.__setattr__(self, "_hash", _hash)
91+
92+
return _hash
93+
94+
def __repr__(self, *args, **kwargs):
95+
r"""
96+
Identical to dict.__repr__().
97+
"""
98+
99+
body = super().__repr__(*args, **kwargs)
100+
klass = self.__class__
101+
102+
if klass == frozendict:
103+
name = f"frozendict.{klass.__name__}"
104+
else:
105+
name = klass.__name__
106+
107+
return f"{name}({body})"
108+
109+
def copy(self):
110+
r"""
111+
Return the object itself, as it's an immutable.
112+
"""
113+
114+
klass = self.__class__
115+
116+
if klass == frozendict:
117+
return self
118+
119+
return klass(self)
120+
121+
def __copy__(self, *args, **kwargs):
122+
r"""
123+
See copy().
124+
"""
125+
126+
return self.copy()
127+
128+
def __deepcopy__(self, memo, *args, **kwargs):
129+
r"""
130+
As for tuples, if hashable, see copy(); otherwise, it returns a
131+
deepcopy.
132+
"""
133+
134+
klass = self.__class__
135+
return_copy = klass == frozendict
136+
137+
if return_copy:
138+
try:
139+
hash(self)
140+
except TypeError:
141+
return_copy = False
142+
143+
if return_copy:
144+
return self.copy()
145+
146+
tmp = deepcopy(dict(self))
147+
148+
return klass(tmp)
149+
150+
def __reduce__(self, *args, **kwargs):
151+
r"""
152+
Support for `pickle`.
153+
"""
154+
155+
return (self.__class__, (dict(self),))
156+
157+
def set(self, key, val):
158+
new_self = deepcopy(dict(self))
159+
new_self[key] = val
160+
161+
return self.__class__(new_self)
162+
163+
def setdefault(self, key, default=None):
164+
if key in self:
165+
return self
166+
167+
new_self = deepcopy(dict(self))
168+
169+
new_self[key] = default
170+
171+
return self.__class__(new_self)
172+
173+
def delete(self, key):
174+
new_self = deepcopy(dict(self))
175+
del new_self[key]
176+
177+
if new_self:
178+
return self.__class__(new_self)
179+
180+
return self.__class__()
181+
182+
def _get_by_index(self, collection, index):
183+
try:
184+
return collection[index]
185+
except IndexError:
186+
maxindex = len(collection) - 1
187+
name = self.__class__.__name__
188+
raise IndexError(
189+
f"{name} index {index} out of range {maxindex}"
190+
) from None
191+
192+
def key(self, index=0):
193+
collection = tuple(self.keys())
194+
195+
return self._get_by_index(collection, index)
196+
197+
def value(self, index=0):
198+
collection = tuple(self.values())
199+
200+
return self._get_by_index(collection, index)
201+
202+
def item(self, index=0):
203+
collection = tuple(self.items())
204+
205+
return self._get_by_index(collection, index)
206+
207+
def __setitem__(self, key, val, *args, **kwargs):
208+
raise TypeError(
209+
f"'{self.__class__.__name__}' object doesn't support item "
210+
"assignment"
211+
)
212+
213+
def __delitem__(self, key, *args, **kwargs):
214+
raise TypeError(
215+
f"'{self.__class__.__name__}' object doesn't support item "
216+
"deletion"
217+
)
218+
219+
220+
def frozendict_or(self, other, *args, **kwargs):
221+
res = {}
222+
res.update(self)
223+
res.update(other)
224+
225+
return self.__class__(res)
226+
227+
228+
frozendict.__or__ = frozendict_or
229+
frozendict.__ior__ = frozendict_or
230+
231+
try:
232+
frozendict.__reversed__
233+
except AttributeError:
234+
def frozendict_reversed(self, *args, **kwargs):
235+
return reversed(tuple(self))
236+
237+
238+
frozendict.__reversed__ = frozendict_reversed
239+
240+
frozendict.clear = immutable
241+
frozendict.pop = immutable
242+
frozendict.popitem = immutable
243+
frozendict.update = immutable
244+
frozendict.__delattr__ = immutable
245+
frozendict.__setattr__ = immutable
246+
frozendict.__module__ = 'frozendict'
247+
248+
__all__ = (frozendict.__name__,)

0 commit comments

Comments
 (0)