Difference between MappingProxyType and PEP 416 frozendict
Asked Answered
P

4

38

While frozendict was rejected, a related class types.MappingProxyType was added to public API in python 3.3.

I understand MappingProxyType is just a wrapper around the underlying dict, but despite that isn't it functionally equivalent to frozendict?

In other words, what's the substantive difference between the original PEP 416 frozendict and this:

from types import MappingProxyType
def frozendict(*args, **kwargs):
  return MappingProxyType(dict(*args, **kwargs))

Of course MappingProxyType is not hashable as is, but just as the PEP suggested for frozendict, it can be made hashable after ensuring that all its values are hashable (MappingProxyType cannot be subclassed, so it would be require composition and forwarding of methods).

Pietism answered 22/1, 2017 at 19:16 Comment(0)
B
31

TL;DR

MappingProxyType is a read only proxy for mapping (e.g. dict) objects.

frozendict is an immutable dict

Answer

The proxy pattern is (quoting wikipedia):

A proxy, in its most general form, is a class functioning as an interface to something else.

The MappingProxyType is just a simple proxy (i.e. interface) to access the real object (the real map, which on our example is dict).

the suggested frozendict object is just as set is to frozenset. a read only (immutable) object which can only be changed upon creation.

So why do we need MappingProxyType? example use case is where you want to pass a dictionary to another function but without it able to change your dictionary, it act as a read only proxy, (quoting python docs):

Read-only proxy of a mapping. It provides a dynamic view on the mapping’s entries, which means that when the mapping changes, the view reflects these changes.

lets see some example usage of the MappingProxyType

In [1]: from types import MappingProxyType
In [2]: d = {'a': 1, 'b': 2}
In [3]: m = MappingProxyType(d)
In [4]: m['a']
Out[4]: 1
In [5]: m['a'] = 5
TypeError: 'mappingproxy' object does not support item assignment
In [6]: d['a'] = 42
In [7]: m['a']
Out[7]: 42
In [8]: for i in m.items():
...:     print(i)

('a', 42)
('b', 2)

Update:

because the PEP did not make it into python, we cannot know for sure what the implementation that would be. by looking at the PEP we see that:

frozendict({'a': {'b': 1}})

would raise an exception as {'b': 1} is not hashable value, but on your implementation it will create the object. of-course, you can add a validation for the value as noted on the PEP.

I assume part of the PEP was memory optimization and implementation of this kind of frozendict could have benefit from the performance of dict comparison using the __hash__ implementation.

Beadledom answered 22/1, 2017 at 19:48 Comment(7)
Yes, but my question was whether there's any substantial difference between the rejected frozendict and the one you can create as a thin wrapper around MappingProxyType. Can you give example of some code that would work with the (rejected) PEP 416 frozendict but would not work with the frozendict that I defined in my question?Pietism
frozendict would enable to use dictionaries as key or make a set of dictionaries. MappingProxyType objects can't be used as dict key or set elements since they could be mutated indirectly during their life cycle, potentially breaking the uniqueness invariant.Geiger
So, the differences so far are 1) MPT isn't hashable, and thus has a wider range of acceptable keyvalues, and 2) MPT isn't subclassable. Why isn't it subclassable?Ionone
@Dubslow. If I had to guess, it's because the C magic that implements it says so. Here is the C interface: docs.python.org/3/c-api/dict.html#c.PyDictProxy_NewRacism
Well, in theory you can create a MappingProxyType this way: MappingProxyType({1: 2}). The original dict is not accessible by anything after this code. The real problem with MappingProxyType is that it is very slow.Semasiology
@MarcoSulla real world usually involves nested mutable objects. Such as {'a':{'b': [1,2]}} which might lead to undesired results when using MappingProxyType. Unlike correctness, Speed is not always a concern.Beadledom
@Beadledom yes, and this is normal. It's not required that all the objects in the immutable object must be immutable. For example, tuple is an immutable type, but you can have a tuple of lists. In this case, the tuple simply is not hashable. If you want deep immutability, there's gelidum: github.com/diegojromerolopez/gelidumSemasiology
F
11

MappingProxyType add immutability only on a first level:

>>> from types import MappingProxyType
>>> d = {'a': {'b': 1}}
>>> md = MappingProxyType(d)
>>> md
mappingproxy({'a': {'b': 1}})
>>> md['a']['b']
1
>>> md['a']['b'] = 3
>>> md['a']['b']
3
Faenza answered 30/10, 2019 at 12:23 Comment(1)
This is obviously the same with frozendict...Semasiology
S
3

MappingProxyType is also terribly slow. I suggest you to use frozendict

PS: I'm the new owner of the package

Semasiology answered 28/7, 2021 at 12:56 Comment(0)
R
1

One thing I've noticed is that frozendict.copy supports add/replace (limited to string keys), whereas MappingProxyType.copy does not. For instance:

d = {'a': 1, 'b': 2} 

from frozendict import frozendict
fd = frozendict(d)
fd2 = fd.copy(b=3, c=5)

from types import MappingProxyType
mp = MappingProxyType(d)
# mp2 = mp.copy(b=3, c=5) => TypeError: copy() takes no keyword arguments
# to do that w/ MappingProxyType we need more biolerplate
temp = dict(mp)
temp.update(b=3, c=5)
mp2 = MappingProxyType(temp)

Note: none of these two immutable maps supports "remove and return new immutable copy" operation.

Rundown answered 13/8, 2019 at 14:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.