How to create an immutable dictionary in python?

Question:

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 ?

Asked By: Yugal Jindle

||

Answers:

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.

Answered By: NPE

Regarding the relationship between hashability and mutability:

To be useful, a hash implementation needs to fulfil the following properties:

  1. The hash value of two objects that compare equal using == must be equal.

  2. 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:

  1. Use a collections.namedtuple() instance instead.

  2. Use a user-defined class with read-only properties.

  3. 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.

Answered By: Sven Marnach

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/

Answered By: Yugal Jindle

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

PS: I’m the new maintainer of the package.

Answered By: Marco Sulla

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.

Answered By: LeoRochael

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

About the sub-class inheriting immutable rules from the parent, you might wish to look into:

  • Interface class contracting & inheritance. Technically you can increase code-safety by making strict rules through inherited interface class or classes*.
  • Constructors
  • Maybe even defining the class as a singleton could help (one class can be instantiated only once, aka to a one/single object), Python support for singleton has been getting a bit more better during last years.
  • Automatic Programming / Code generation tactics & best-practises

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).

Although this does not make it "real constant", it still offers you protection for the object being changed.

  • One additional thing if focusing on the code-safety by wrapping the dictionary/dictionaries inside this type of class, is that might find it feasible to look how to make a singleton class as well as how to use a constructor, def init() in your problem.

Depending on your code safety needs, you might still wish to:

  • instantiate the whole dictionary from read-only json (that ensure the structure etc.), use json schema-definition to give it another layer of validation as well.
  • you might wish to encode your class instance & its dictionary objects in Python, then provide only Read-functionality which does decoding before reading the dict. This is slight perf impact, but if your dictionary contains feasible amount of data (not "big data"), and there is no need for real-time high-perf + the amount of this type of dictionaries is rather moderate (not tens of thousands/millions, decoding it in read-function beginning should not be a problem.

*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)

Answered By: keljaus_aeon