43

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

5
  • 1
    stackoverflow.com/questions/8752451/… may help you Commented Sep 26, 2013 at 8:29
  • 3
    A frozendict implementation looks rather trivial: github.com/slezica/python-frozendict/blob/master/frozendict/… Commented Sep 26, 2013 at 8: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? Commented Sep 27, 2013 at 6:28
  • 1
    @sshilovsky Nope, instead you could derive an user dict and override whatever & you can use where ever it is applicable. Commented Sep 27, 2013 at 12:52
  • 2
    Does this answer your question? What would a "frozen dict" be? Commented Feb 3, 2021 at 23:18

6 Answers 6

47

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)
Sign up to request clarification or add additional context in comments.

3 Comments

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.
I think it works exactly like you describe already. dict_wrapper[key] returns whatever you've put there, which might as well be a raw dict.
Great answer! It's worth mentioning: (1) Modern python wants collections.abc.Mapping, not collections.Mapping which has been deprecated. (2) collections.abc.Mapping simply doesn't have __setitem__ or ilk methods defined, so attempts to store data to the mapping will result in a TypeError exception. (3) If you want something to make an entire tree of dictionaries read-only, you need to make __gititem__ return a read-only wrapper for lists and dicts. Additionally, you'll need to define a read-only iterator for __iter__ to return.
41

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__

6 Comments

To allow for copying them using copy.copy and copy.deepcopy you might want to add the following two lines: __copy__ = dict.copy and __deepcopy__ = copy._deepcopy_dispatch.get(dict). Not much tested though.
right, but how to initialize this or how to make existing dict use this?
ro_dict = ReadOnlyDict(rw_dict)
Doing del __readonly__ will mess with pickling of the methods, both unbound and bound, if that even comes up. Also all names of the form __*__ are reserved by Python, and are free to break at any time. So I would call it _readonly, define it outside the class, and just leave it in the module it's defined in instead of del-ing it. (Bound which are that function will still be unpickleable, but that's due to a weirdly broken approach in Python's pickling of bound methods - unbound references to those methods will be pickleable.)
Cheers, just noticed the edit, happy to help!
|
11

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.

3 Comments

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.
If he needs, he can modify them too
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.
4

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.

1 Comment

What about instance = MyReadOnlyDict(); instance.data['a'] = 'b'
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

Comments

0

Belated answer. You can use a combination of property and MappingProxyType to protect the .data field from modifications.

from types import MappingProxyType

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

    @property
    def data(self):
        return MappingProxyType(self._data)

    def update(self, key, value):
        self._data[key] = value

a = A()
a.update("foo", 1)
a.update("bar", 2)
print(f"a.data={a.data}")

# Attempt to modify a.data directly will fail
try:
    a.data["bar"] = 20
except TypeError as error:
    print(f"Error: {error}")

# Attempt to assign something else to a.data, will also fail
try:
    a.data = {}
except AttributeError as error:
    print(f"Error: {error}")

Output:

a.data={'foo': 1, 'bar': 2}
Error: 'mappingproxy' object does not support item assignment
Error: property 'data' of 'A' object has no setter

Notes

  • The A.data is not a regular attribute, it is a property. Since there is no setter, it is also a read-only property.
  • A.data returns a MappingProxy instead of the dictionary itself. That means code outside of the class will not be able to modify the dictionary's contents.
  • In order to modify the dictionary, I added the method update() as an example
  • If people want to, they can modify A._data directly, there is nothing we can do to prevent that.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.