Reputation: 45656
I want to subclass dict
in python such that all the dictionaries of the sub-class are immutable.
I don't understand how does __hash__
affects the immutability, since in my understanding it just signifies the equality or non-equality of objects !
So, can __hash__
be used to implement immutability ? How ?
Update:
Objective is that common response from an API is available as a dict, which has to be shared as a global variable. So, that needs to be intact no matter what ?
Upvotes: 44
Views: 66115
Reputation: 15930
In frozendict, hash is simply implemented following the rejected PEP 416 of Victor Stinner:
def __hash__(self):
try:
fs = frozenset(self.items())
except TypeError:
hash = -1
else:
hash = hash(fs)
if hash == -1:
raise TypeError(f"not all values are hashable: '{self.__class__.__name__}'")
return hash
About hashability and equality, they are not the same.
Two objects can be equal even if they are mutable.
For example, {1:2, 3:4} == {3:4, 1:2}
Usually, hashable objects are immutable, but it's not required.
Furthermore, usually objects with the same hash are also equal, but this is not always true.
For example, hash(-1) == hash(-2) == -2
.
This is called hash collision.
Further reading:
https://docs.python.org/3/glossary.html#term-hashable
https://en.wikipedia.org/wiki/Hash_collision
PS: I'm the new maintainer of the frozendict
package.
Upvotes: 19
Reputation: 13
About the sub-class inheriting immutable rules from the parent, you might wish to look into:
Below is different mechanisms (I could quickly imagine, probably not comprehensive list!) that you can increase code-safety in Python (as it is not strictly typed, nor it has honestly very good mechanisms for writing bullet-proof code in such way that this type of things could be forced already by the compiler, as it has no compiler).
As many already answered, and this is not a direct answer for the question, but Python offers means to use Hashing to any object (dictionary is just one object in Python as well), so technically you can force any object to be hashed and ensure the checksum match anytime you wish: https://docs.python.org/3/library/functions.html#hash (hash diffing)
Also, technically you can protect your dictionary also by wrap data-structures inside inside a class (usually called Model) and then using final type: https://docs.python.org/3.8/library/typing.html#typing.Final
Although this does not make it "real constant", it still offers you protection for the object being changed.
Depending on your code safety needs, you might still wish to:
*Last time I checked, Python still had no true built-in Interface-class support, its from an external library.
Through built-in lib called abc (abstract base class) you can "imitate" true interface implementation, although this as the name says is for making classes abstract, not for creating interfaces that are in my opinion better way to do ruffly the same, without creating plethora of code-locks that echo as a code-duplication on the child object level.
Also, with interface-class you can loop things by it, I think abstract classes cannot be used in such a sense.
Let'say: IConstantSingleton (contract) --> inherit to ConstSingletonBaseObject (base/parent) --> inherit to any various children (instantiated child objects), where children can be required to fulfill the contract details through passing the responsibilities from abstraction of the base/parent), or from the virtual definition in base/parent function(s) (that Python has a little support nowadays already), that allows you to implement only overrides (less-code duplication!).
Surely, you can contract also child-classes directly, without inheriting the contract, especially if there is such virtual override need for example, additional contract for those special-snowflakes can increase the code-safety and robustness (and future development based on it)
Upvotes: -3
Reputation: 19083
It is possible to create immutable dict
using just standard library.
from types import MappingProxyType
power_levels = MappingProxyType(
{
"Kevin": 9001,
"Benny": 8000,
}
)
See source of idea with more detailed explanation
Upvotes: 43
Reputation: 15177
Since Python 3.3, it's possible to use MappingProxyType
to create an immutable mapping:
>>> from types import MappingProxyType
>>> MappingProxyType({'a': 1})
mappingproxy({'a': 1})
>>> immutable_mapping = MappingProxyType({'a': 1})
>>> immutable_mapping['a']
1
>>> immutable_mapping['b'] = 2
Traceback (most recent call last):
(...)
TypeError: 'mappingproxy' object does not support item assignment
It's not hashable so you can't use it as a dictionary key (and it's "final", so you can't subclass it to override __hash__
), but it's good enough if you want an immutable mapping to prevent accidental modification of a global value (like a class default attribute).
Careful not to add mutable values that could themselves be modified.
Upvotes: 9
Reputation: 45656
I found a Official reference : suggestion contained in a rejected PEP.
class imdict(dict):
def __hash__(self):
return id(self)
def _immutable(self, *args, **kws):
raise TypeError('object is immutable')
__setitem__ = _immutable
__delitem__ = _immutable
clear = _immutable
update = _immutable
setdefault = _immutable
pop = _immutable
popitem = _immutable
Attribution : http://www.python.org/dev/peps/pep-0351/
Upvotes: 17
Reputation: 601659
Regarding the relationship between hashability and mutability:
To be useful, a hash implementation needs to fulfil the following properties:
The hash value of two objects that compare equal using ==
must be equal.
The hash value may not change over time.
These two properties imply that hashable classes cannot take mutable properties into account when comparing instances, and by contraposition that classes which do take mutable properties into account when comparing instances are not hashable. Immutable classes can be made hashable without any implications for comparison.
All of the built-in mutable types are not hashable, and all of the immutable built-in types are hashable. This is mainly a consequence of the above observations.
User-defined classes by default define comparison based on object identity, and use the id()
as hash. They are mutable, but the mutable data is not taken into account when comparing instances, so they can be made hashable.
Making a class hashable does not make it immutable in some magic way. On the contrary, to make a dictionary hashable in a reasonable way while keeping the original comparison operator, you will first need to make it immutable.
Edit: Regarding your update:
There are several ways to provide the equivalent of global immutable dictionary:
Use a collections.namedtuple()
instance instead.
Use a user-defined class with read-only properties.
I'd usually go with something like this:
_my_global_dict = {"a": 42, "b": 7}
def request_value(key):
return _my_global_dict[key]
By the leading underscore, you make clear that _my_global_dict
is an implementation detail not to be touched by application code. Note that this code would still allow to modify dictionary values if they happen to be mutable objects. You could solve this problem by returning copy.copy()
s or copy.deepcopy()
s of the values if necessary.
Upvotes: 3
Reputation: 500357
So, can
__hash__
be used to implement immutability ?
No, it can't. The object can be made mutable (or not) irrespective of what its __hash__
method does.
The relationship between immutable objects and __hash__
is that, since an immutable object cannot be changed, the value returned by __hash__
remains constant post-construction. For mutable objects, this may or may not be the case (the recommended practice is that such objects simply fail to hash).
For further discussion, see Issue 13707: Clarify hash()
constency period.
Upvotes: 7