How to make dictionary read-only?

Question:

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

Asked By: sshilovsky

||

Answers:

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.

Answered By: JadedTuna

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)
Answered By: mouad

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

def _readonly(self, *args, **kwargs):
    raise RuntimeError("Cannot modify ReadOnlyDict")

class ReadOnlyDict(dict):
    __setitem__ = _readonly
    __delitem__ = _readonly
    pop = _readonly
    popitem = _readonly
    clear = _readonly
    update = _readonly
    setdefault = _readonly

My previous (worse) implementation was as follows (thanks @mtraceur for the great remarks!):

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__
Answered By: Marcin RaczyƄski

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

Answered By: Polv

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.

Answered By: Shital Shah