22

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 ?

wjandrea
  • 28,235
  • 9
  • 60
  • 81
Yugal Jindle
  • 44,057
  • 43
  • 129
  • 197
  • 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? – Sven Marnach Jun 13 '12 at 11:54
  • 4
    How about this link: http://stackoverflow.com/q/2703599/623518 – Chris Jun 13 '12 at 11:55
  • 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). – Joel Cornett Jun 13 '12 at 12:06
  • 2
    @JoelCornett: You would at least also need to overwrite `__delitem__()`, `clear()`, `pop()`, `popitem()`, `setdefault()` and `update()`. – Sven Marnach Jun 13 '12 at 12:16
  • @SvenMarnach: hahaha I forgot about those XD. Yes you would. – Joel Cornett Jun 13 '12 at 12:23
  • @SvenMarnach Is there a simpler way? How come `tuple` is immutable ? – Yugal Jindle Jun 13 '12 at 12:30
  • @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! – Sven Marnach Jun 13 '12 at 12:36
  • This might be of interest: https://github.com/magicstack/immutables – Superdooperhero Dec 28 '19 at 08:48
  • 1
    Does this answer your question? [What would a "frozen dict" be?](https://stackoverflow.com/questions/2703599/what-would-a-frozen-dict-be) – ggorlen Aug 03 '22 at 20:47
  • The [immutables](https://github.com/MagicStack/immutables) package is now available and it is very similar to working with Python dicts. – DOT Jan 24 '23 at 10:09

7 Answers7

18

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

Konstantin Smolyanin
  • 17,579
  • 12
  • 56
  • 56
16

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/

Mike
  • 58,961
  • 76
  • 175
  • 221
Yugal Jindle
  • 44,057
  • 43
  • 129
  • 197
10

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("Not all values are hashable.")
    
    return hash

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

Marco Sulla
  • 15,299
  • 14
  • 65
  • 100
  • 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! – AmphotericLewisAcid Jun 19 '23 at 17:14
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.

NPE
  • 486,780
  • 108
  • 951
  • 1,012
  • Well, user-defined classes are both mutable and hashable by default, but the hash will remain constant anyway! – Sven Marnach Jun 13 '12 at 11:57
  • @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... – NPE Jun 13 '12 at 12:03
  • I'm simply suggesting that the last sentence of your answer is somewhat incomplete. There's more to the relationship between immutability and hashability. – Sven Marnach Jun 13 '12 at 12:09
  • 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. – Sven Marnach Jun 13 '12 at 12:13
  • Can you provide some sample code `How to write an immutable dict ?` – Yugal Jindle Jun 13 '12 at 12:30
5

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.

LeoRochael
  • 14,191
  • 6
  • 32
  • 38
3

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.

Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
  • So, please give some code that can make it `immutable` ? Since this answers just half the question ! – Yugal Jindle Jun 13 '12 at 12:46
  • 1
    @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.) – Sven Marnach Jun 13 '12 at 12:49
  • @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. – Sven Marnach Jun 13 '12 at 13:00
  • 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. – Yugal Jindle Jun 13 '12 at 13:14
  • 1
    @YugalJindle: Sorry for disobeying your orders. I don't think I can help you any further. Good luck! – Sven Marnach Jun 13 '12 at 13:27
  • 3
    I will be looking for the solution. `SO` is a place for helping not ordering.. Thanks for your immense out of the point efforts ! – Yugal Jindle Jun 14 '12 at 01:58
0

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)

Happy C0d1ng!

  • 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. – keljaus_aeon Aug 29 '23 at 10:24