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 _module_name = "frozendict" # noinspection PyPep8Naming 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)) # noinspection PyMethodParameters def __new__(e4b37cdf_d78a_4632_bade_6f0579d8efac, *args, **kwargs): cls = e4b37cdf_d78a_4632_bade_6f0579d8efac has_kwargs = bool(kwargs) continue_creation = True self = None # 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 # noinspection PyMissingConstructor 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"{_module_name}.{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 = 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 = dict(self) new_self[key] = default return self.__class__(new_self) def delete(self, key): new_self = 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: # noinspection PyStatementEffect frozendict.__reversed__ except AttributeError: # pragma: no cover 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__ = _module_name __all__ = (frozendict.__name__,)