-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
3208ed8
commit a63cad5
Showing
1 changed file
with
248 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,248 @@ | ||
from copy import deepcopy | ||
|
||
|
||
def immutable(self, *args, **kwargs): | ||
r""" | ||
Function for not implemented method since the object is immutable | ||
""" | ||
|
||
raise AttributeError( | ||
f"'{self.__class__.__name__}' object is read-only" | ||
) | ||
|
||
|
||
_empty_frozendict = None | ||
|
||
|
||
class frozendict(dict): | ||
r""" | ||
A simple immutable dictionary. | ||
The API is the same as `dict`, without methods that can change the | ||
immutability. In addition, it supports __hash__(). | ||
""" | ||
|
||
__slots__ = ( | ||
"_hash", | ||
) | ||
|
||
@classmethod | ||
def fromkeys(cls, *args, **kwargs): | ||
r""" | ||
Identical to dict.fromkeys(). | ||
""" | ||
|
||
return cls(dict.fromkeys(*args, **kwargs)) | ||
|
||
def __new__(e4b37cdf_d78a_4632_bade_6f0579d8efac, *args, **kwargs): | ||
cls = e4b37cdf_d78a_4632_bade_6f0579d8efac | ||
|
||
has_kwargs = bool(kwargs) | ||
continue_creation = True | ||
|
||
# check if there's only an argument and it's of the same class | ||
if len(args) == 1 and not has_kwargs: | ||
it = args[0] | ||
|
||
# no isinstance, to avoid subclassing problems | ||
if it.__class__ == frozendict and cls == frozendict: | ||
self = it | ||
continue_creation = False | ||
|
||
if continue_creation: | ||
self = dict.__new__(cls, *args, **kwargs) | ||
|
||
dict.__init__(self, *args, **kwargs) | ||
|
||
# empty singleton - start | ||
|
||
if self.__class__ == frozendict and not len(self): | ||
global _empty_frozendict | ||
|
||
if _empty_frozendict is None: | ||
_empty_frozendict = self | ||
else: | ||
self = _empty_frozendict | ||
continue_creation = False | ||
|
||
# empty singleton - end | ||
|
||
if continue_creation: | ||
object.__setattr__(self, "_hash", -1) | ||
|
||
return self | ||
|
||
def __init__(self, *args, **kwargs): | ||
pass | ||
|
||
def __hash__(self, *args, **kwargs): | ||
r""" | ||
Calculates the hash if all values are hashable, otherwise | ||
raises a TypeError. | ||
""" | ||
|
||
if self._hash != -1: | ||
_hash = self._hash | ||
else: | ||
fs = frozenset(self.items()) | ||
_hash = hash(fs) | ||
|
||
object.__setattr__(self, "_hash", _hash) | ||
|
||
return _hash | ||
|
||
def __repr__(self, *args, **kwargs): | ||
r""" | ||
Identical to dict.__repr__(). | ||
""" | ||
|
||
body = super().__repr__(*args, **kwargs) | ||
klass = self.__class__ | ||
|
||
if klass == frozendict: | ||
name = f"frozendict.{klass.__name__}" | ||
else: | ||
name = klass.__name__ | ||
|
||
return f"{name}({body})" | ||
|
||
def copy(self): | ||
r""" | ||
Return the object itself, as it's an immutable. | ||
""" | ||
|
||
klass = self.__class__ | ||
|
||
if klass == frozendict: | ||
return self | ||
|
||
return klass(self) | ||
|
||
def __copy__(self, *args, **kwargs): | ||
r""" | ||
See copy(). | ||
""" | ||
|
||
return self.copy() | ||
|
||
def __deepcopy__(self, memo, *args, **kwargs): | ||
r""" | ||
As for tuples, if hashable, see copy(); otherwise, it returns a | ||
deepcopy. | ||
""" | ||
|
||
klass = self.__class__ | ||
return_copy = klass == frozendict | ||
|
||
if return_copy: | ||
try: | ||
hash(self) | ||
except TypeError: | ||
return_copy = False | ||
|
||
if return_copy: | ||
return self.copy() | ||
|
||
tmp = deepcopy(dict(self)) | ||
|
||
return klass(tmp) | ||
|
||
def __reduce__(self, *args, **kwargs): | ||
r""" | ||
Support for `pickle`. | ||
""" | ||
|
||
return (self.__class__, (dict(self),)) | ||
|
||
def set(self, key, val): | ||
new_self = deepcopy(dict(self)) | ||
new_self[key] = val | ||
|
||
return self.__class__(new_self) | ||
|
||
def setdefault(self, key, default=None): | ||
if key in self: | ||
return self | ||
|
||
new_self = deepcopy(dict(self)) | ||
|
||
new_self[key] = default | ||
|
||
return self.__class__(new_self) | ||
|
||
def delete(self, key): | ||
new_self = deepcopy(dict(self)) | ||
del new_self[key] | ||
|
||
if new_self: | ||
return self.__class__(new_self) | ||
|
||
return self.__class__() | ||
|
||
def _get_by_index(self, collection, index): | ||
try: | ||
return collection[index] | ||
except IndexError: | ||
maxindex = len(collection) - 1 | ||
name = self.__class__.__name__ | ||
raise IndexError( | ||
f"{name} index {index} out of range {maxindex}" | ||
) from None | ||
|
||
def key(self, index=0): | ||
collection = tuple(self.keys()) | ||
|
||
return self._get_by_index(collection, index) | ||
|
||
def value(self, index=0): | ||
collection = tuple(self.values()) | ||
|
||
return self._get_by_index(collection, index) | ||
|
||
def item(self, index=0): | ||
collection = tuple(self.items()) | ||
|
||
return self._get_by_index(collection, index) | ||
|
||
def __setitem__(self, key, val, *args, **kwargs): | ||
raise TypeError( | ||
f"'{self.__class__.__name__}' object doesn't support item " | ||
"assignment" | ||
) | ||
|
||
def __delitem__(self, key, *args, **kwargs): | ||
raise TypeError( | ||
f"'{self.__class__.__name__}' object doesn't support item " | ||
"deletion" | ||
) | ||
|
||
|
||
def frozendict_or(self, other, *args, **kwargs): | ||
res = {} | ||
res.update(self) | ||
res.update(other) | ||
|
||
return self.__class__(res) | ||
|
||
|
||
frozendict.__or__ = frozendict_or | ||
frozendict.__ior__ = frozendict_or | ||
|
||
try: | ||
frozendict.__reversed__ | ||
except AttributeError: | ||
def frozendict_reversed(self, *args, **kwargs): | ||
return reversed(tuple(self)) | ||
|
||
|
||
frozendict.__reversed__ = frozendict_reversed | ||
|
||
frozendict.clear = immutable | ||
frozendict.pop = immutable | ||
frozendict.popitem = immutable | ||
frozendict.update = immutable | ||
frozendict.__delattr__ = immutable | ||
frozendict.__setattr__ = immutable | ||
frozendict.__module__ = 'frozendict' | ||
|
||
__all__ = (frozendict.__name__,) |