How to create an immutable dictionary in python?
Asked Answered
W

7

33

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 ?

Wreckful answered 13/6, 2012 at 11:52 Comment(10)
This has been asked many times before (unfortunately, I don't have a link handy right now). For most problems, trying to make an immutable dictionary is the wrong approach. What exactly are you trying to achieve?Tacita
How about this link: https://mcmap.net/q/46325/-what-would-a-quot-frozen-dict-quot-be/623518Inarch
I would just override the __setitem__() method of your dict. Note however, that this doesn't guarantee that the values of your dict will be immutable (say you have values that are lists, for example).Feverish
@JoelCornett: You would at least also need to overwrite __delitem__(), clear(), pop(), popitem(), setdefault() and update().Tacita
@SvenMarnach: hahaha I forgot about those XD. Yes you would.Feverish
@SvenMarnach Is there a simpler way? How come tuple is immutable ?Wreckful
@YugalJindle: Whether there is a simpler way to achieve what you are trying to achieve depends on what you are trying to achieve. Most probably, yes!Tacita
This might be of interest: github.com/magicstack/immutablesBelier
Does this answer your question? What would a "frozen dict" be?Tramel
The immutables package is now available and it is very similar to working with Python dicts.Vernievernier
W
19

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/

Wreckful answered 14/6, 2012 at 4:42 Comment(5)
This PEP was rejected btwTavis
This is bad answer, definitely not official in any way. You shouldn't be deriving from dict and there is no need to override so many methods unnecessarily.Morpheus
Edited to remove claim of being an "official reference"Error
@ShitalShah deriving from dict is not the ideal solution, but imdict in this way is more immutable, is faster and many other Python tools will automagically works, like json. Practicality beats purity.Privacy
bad solution, because imdict is an instance of MutableMappingQuanta
F
34

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

Fourteenth answered 2/6, 2022 at 10:15 Comment(5)
If the value is a dict, does the value also become immutable?Hartzke
@Hartzke no, you should use another MappingProxyType. That, by the way, is a very slow beast.Privacy
will MappingProxyType not be deprecated in future?Quanta
@Quanta I know nothing about possible deprecation. Can you provide some proofs (links to discussions etc) or this was just a question without any background?Fourteenth
@KonstantinSmolyanin I am just askingQuanta
W
19

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/

Wreckful answered 14/6, 2012 at 4:42 Comment(5)
This PEP was rejected btwTavis
This is bad answer, definitely not official in any way. You shouldn't be deriving from dict and there is no need to override so many methods unnecessarily.Morpheus
Edited to remove claim of being an "official reference"Error
@ShitalShah deriving from dict is not the ideal solution, but imdict in this way is more immutable, is faster and many other Python tools will automagically works, like json. Practicality beats purity.Privacy
bad solution, because imdict is an instance of MutableMappingQuanta
P
17

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.

Privacy answered 15/1, 2022 at 21:9 Comment(1)
As of 2023, this is the best answer IMO! Builds on what already existed in PEP 416, and unlike the MappingProxyType objects in the stdlib it can be pickled. And at a meta level, it tackles the answer directly!Popele
C
7

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.

Cuffs answered 13/6, 2012 at 11:55 Comment(5)
Well, user-defined classes are both mutable and hashable by default, but the hash will remain constant anyway!Tacita
@SvenMarnach: What you're saying is true. What I'm saying is also true. TBH, I don't see where your line of reasoning is going...Cuffs
I'm simply suggesting that the last sentence of your answer is somewhat incomplete. There's more to the relationship between immutability and hashability.Tacita
Obviously, I need to be more specific. A hash value that might change over time is quite useless in Python. The main purpose of hashes is that hashable objects may be used in sets and dictionaries. If the hash value changes, this will fail in strange and unpredictable ways. So all sensible hash implementations have the property that the hash value remains constant, no exceptions.Tacita
Can you provide some sample code How to write an immutable dict ?Wreckful
W
7

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.

Weal answered 25/5, 2022 at 2:19 Comment(0)
T
2

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.

Tacita answered 13/6, 2012 at 12:34 Comment(6)
So, please give some code that can make it immutable ? Since this answers just half the question !Wreckful
@YugalJindle: As I said numerous times before, using an immutable dictionary is most probably the wrong solution for your problem, but we can only tell you if you tell us what your problem actually is. (Moreover, I get the impression that you are not really interested in a good solution, and that you don't really pay attention to the answers and comments. Otherwise, you would already have found some example code, which is linked in the comments above.)Tacita
@YugalJindle: No, it's certainly not the only solution in your case. And again, the code is for an immutable dictionary linked in the second comment to your question.Tacita
I really don't understand how does your code implements a immutable dict. You wrote a wrapper method for key query, but I want to give it away as a proper dict object that can perform everything except write operations. I said in the question subclass. Also, I can't use deepcopy since its very slow.Wreckful
@YugalJindle: Sorry for disobeying your orders. I don't think I can help you any further. Good luck!Tacita
I will be looking for the solution. SO is a place for helping not ordering.. Thanks for your immense out of the point efforts !Wreckful
L
-3

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)

Light answered 29/8, 2023 at 9:43 Comment(1)
Also one rather strong forcing is to generate default uuid for such DataModel, and when you construct new object instances after the initial one (with new uuid) and then continue working with those new N object instances, you can verify against the original instance the contents that should never be changed and raise Exception. This technically can be done for example in your Load phase, between init and run phase, and if you exclude it from production implentation behind dev-flag, it wont have perf impact, but it surely forces to handle the data ”right way” as you designed/wished.Light

© 2022 - 2024 — McMap. All rights reserved.