max
max

Reputation: 52323

Difference between MappingProxyType and PEP 416 frozendict

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).

Upvotes: 47

Views: 19893

Answers (4)

Marco Sulla
Marco Sulla

Reputation: 15930

TL;DR I suggest you to use frozendict. Github: https://github.com/Marco-Sulla/python-frozendict

MappingProxyType once was terribly slow. Now it's no more, but has still some lacks compared to frozendict:

  1. it does not have an hash, so you can't use it in set, dict etc. I remember that at a certain point it was introduced a "fake" hash, but it seems it was removed (I checked it in Python 3.13).
  2. if frozendict has an hash, any operation that returns a shallow copy simply returns the frozendict itself. On the contrary, MappingProxyType performs a normal copy of the internal dict, so it's much more slow
  3. frozendict has some additional speedups and features for a functional programming style, as a set and delete methods and a deepfreeze module function, for converting an object in its immutable counterpart, converting also any nested mutable object.

PS: I'm the new owner of the package

Upvotes: 3

Alexey Shrub
Alexey Shrub

Reputation: 1314

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

Upvotes: 12

trybik
trybik

Reputation: 492

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.

Upvotes: 1

ShmulikA
ShmulikA

Reputation: 3754

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.

Upvotes: 35

Related Questions