36

I have a class like:

class A:
    def __init__(self):
        self.data = {}

and at some moment I want to prohibit self.data fields modification.

I've read in PEP-416 rejection notice that there are a lot of ways to do it. So I'd like to find what they are.

I tried this:

a = A()
a.data = types.MappingProxyType(a.data)

That should work but first, its python3.3+ and second, when I do this "prohibition" multiple times I get this:

>>> a.data = types.MappingProxyType(a.data)
>>> a.data = types.MappingProxyType(a.data)
>>> a.data
mappingproxy(mappingproxy({}))

though it would be much better to get just mappingproxy({}) as I am going to "prohibit" a lot of times. Check of isinstance(MappingProxyType) is an option, but I think that other options can exist.

Thanks

Martin Thoma
  • 108,021
  • 142
  • 552
  • 849
sshilovsky
  • 859
  • 1
  • 8
  • 21
  • 1
    http://stackoverflow.com/questions/8752451/how-to-change-the-behavior-of-a-python-dictionarys-setattr may help you – Garfield Sep 26 '13 at 08:29
  • 3
    A frozendict implementation looks rather trivial: https://github.com/slezica/python-frozendict/blob/master/frozendict/__init__.py – georg Sep 26 '13 at 08:36
  • 2
    @codelover Thats right, I can override `__setitem__` with `throw NotImplemented`. Is there guarantee that there will be no way to modify keys or values via any standard `dict` attributes in any python implementation? – sshilovsky Sep 27 '13 at 06:28
  • 1
    @sshilovsky Nope, instead you could derive an user dict and override whatever & you can use where ever it is applicable. – Garfield Sep 27 '13 at 12:52
  • 2
    Does this answer your question? [What would a "frozen dict" be?](https://stackoverflow.com/questions/2703599/what-would-a-frozen-dict-be) – Seanny123 Feb 03 '21 at 23:18

5 Answers5

41

Use collections.Mapping e.g.

import collections

class DictWrapper(collections.Mapping):

    def __init__(self, data):
        self._data = data

    def __getitem__(self, key): 
        return self._data[key]

    def __len__(self):
        return len(self._data)

    def __iter__(self):
        return iter(self._data)
Trilarion
  • 9,942
  • 9
  • 61
  • 98
mouad
  • 63,741
  • 17
  • 112
  • 105
  • Can it be that this only makes the dict itself, but not necessarilty contained dicts immutable? I currently want to make a nested dict representing a config immuatable. – Martin Thoma Apr 07 '17 at 15:34
33

This is full implementation of fast (shallow-)read-only dict:

class ReadOnlyDict(dict):
    def __readonly__(self, *args, **kwargs):
        raise RuntimeError("Cannot modify ReadOnlyDict")
    __setitem__ = __readonly__
    __delitem__ = __readonly__
    pop = __readonly__
    popitem = __readonly__
    clear = __readonly__
    update = __readonly__
    setdefault = __readonly__
    del __readonly__
Marcin Raczyński
  • 901
  • 1
  • 10
  • 14
10

Very easy, you just override default dict's methods!
Here is an example:

class ReadOnlyDict(dict):

    __readonly = False

    def readonly(self, allow=1):
        """Allow or deny modifying dictionary"""
        self.__readonly = bool(allow)

    def __setitem__(self, key, value):

        if self.__readonly:
            raise TypeError, "__setitem__ is not supported"
        return dict.__setitem__(self, key, value)

    def __delitem__(self, key):

        if self.__readonly:
            raise TypeError, "__delitem__ is not supported"
        return dict.__delitem__(self, key)

BTW, you can also remove .pop, .update and other methods you need. Just play around with it.

JadedTuna
  • 1,701
  • 2
  • 18
  • 31
  • How do you apply this "at some moment"? I guess OP needs the dict to be read-write, then make it frozen after some time. – justhalf Sep 26 '13 at 09:15
  • If he needs, he can modify them too – JadedTuna Sep 26 '13 at 13:34
  • 4
    This approach is obvious but is not simple as I have to override almost each `dict` method and even reimplement `dict` views like `.keys()`, `.items()` etc. And what if Guido adds some more in future python versions? Anyway, thanks for the point, but I wish there were more answers. – sshilovsky Sep 27 '13 at 05:33
3

The best way is to derive from UserDict like this:

from collections import UserDict

class MyReadOnlyDict(UserDict):
   def my_set(self, key, val, more_params):
       # do something special
       # custom logic etc
       self.data[key] = val

   def __setitem__(self, key, val):
       raise NotImplementedError('This dictionary cannot be updated')

   def __delitem__(self, key):
       raise NotImplementedError('This dictionary does not allow delete')

The advantage of this method is that you can still have internal methods in your class that can update dictionary by accessing self.data.

Ouroborus
  • 14,339
  • 1
  • 33
  • 57
Shital Shah
  • 55,892
  • 12
  • 218
  • 175
1

How about? It is the update of @mouad 's answer.

import json
from collections import OrderedDict
from collections.abc import Mapping


class ReadOnlyJsonObject(Mapping):
    def __init__(self, data, dumps_kw: dict=None, loads_kw: dict=None):
        if dumps_kw is None:
            dumps_kw = dict()
        if loads_kw is None:
            self._loads_kw = dict(object_pairs_hook=OrderedDict)
        else:
            self._loads_kw = loads_kw

        if isinstance(data, str):
            self._json_string = data
        else:
            self._json_string = json.dumps(data, **dumps_kw)

    @property
    def _data(self):
        return json.loads(self._json_string, **self._loads_kw)

    def __getitem__(self, key):
        return self._data[key]

    def __len__(self):
        return len(self._data)

    def __iter__(self):
        return iter(self._data)

    def __str__(self):
        return self._json_string

Not sure about the performance, though. I use this one in a real project https://github.com/patarapolw/AnkiTools/blob/master/AnkiTools/tools/defaults.py

Polv
  • 1,616
  • 1
  • 16
  • 31