New submission from Joshua Bronson <jabron...@gmail.com>:

As of bpo-40890 (released in Python 3.10), dict views now provide a public 
.mapping attribute, intended to allow users to recover a mappingproxy pointing 
to the original mapping.

However, this new attribute can actually point to the wrong mapping for some 
existing uses of dict views. And since the .mapping attribute is read-only, 
these existing uses have no way to set it to the correct value.

My bidict library (see https://github.com/jab/bidict) provides an example of 
this.

A bidict implements a bidirectional mapping by building on top of two dicts 
(i.e. regular old one-directional mappings) internally -- one for the forward 
direction, and one for the inverse. When you call e.g. keys() or values() on a 
bidict, you get back a dict_keys view from one of the backing dicts, because 
this is a much more optimized implementation of these views than 
collections.abc.KeysView would be:

>>> import bidict
>>> b = bidict.bidict(one=1, two=2)
>>> b
bidict({'one': 1, 'two': 2})
>>> b.keys()
dict_keys(['one', 'two'])
>>> b.values()
dict_keys([1, 2])


However, now that these built-in dict_keys objects provide a .mapping attribute 
in Python 3.10, it points to one of the internal, backing dicts in this case, 
which is an implementation detail, rather than to the bidict instance:

>>> b.keys().mapping  # wrong
mappingproxy({'one': 1, 'two': 2})
>>> b.values().mapping  # wrong
mappingproxy({1: 'one', 2: 'two'})


Instead of the above, you should get:

>>> b.keys().mapping  # corrected:
mappingproxy(bidict({'one': 1, 'two': 2}))
>>> b.values().mapping  # corrected:
mappingproxy(bidict({'one': 1, 'two': 2}))


Since the .mapping attribute is read-only, there's no way for bidict to both 
keep exposing the optimized dict_keys implementations, which up till now have 
been perfectly correct, while now exposing a correct .mapping attribute for 
users of Python 3.10+.

(Other bidict types demonstrate this problem more by exposing even more 
obviously-unintended implementation details via this new .mapping attribute:

>>> f = bidict.FrozenOrderedBidict(b)
>>> f
FrozenOrderedBidict([('one', 1), ('two', 2)])
>>> f.keys().mapping  # ouch
mappingproxy({'one': _Node(prv=..., self=..., nxt=...), 'two': _Node(prv=..., 
self=..., nxt=...)})

Those internal _Node objects were never meant to be exposed to consumers, 
they're an implementation detail.)


It looks like cases like this were not considered when discussing bpo-40890, 
and if they had been, I wonder if the implementation would have been accepted 
as-is.

Here are some potential directions for how to improve things for the future:

1. Expose a way for dict view users like bidict to continue to use optimized 
dict view implementations while opting out of the new .mapping attribute

2. Make the .mapping attribute no longer read-only, so libraries like bidict 
can set it correctly before exposing it to users

3. Merely update the documentation in 
https://docs.python.org/3/library/stdtypes.html#:~:text=dictview.mapping,in%20version%203.10.
 to mention that, because the .mapping attribute is read-only, it may not point 
to the original, intended mapping, but rather some internal mapping that the 
user was not intended to be exposed to.


Looking forward to hearing your thoughts, and thanks for your consideration.

----------
components: Interpreter Core
messages: 405317
nosy: jab, rhettinger
priority: normal
severity: normal
status: open
title: New .mapping attribute is broken for some existing uses of dict views
type: behavior
versions: Python 3.10

_______________________________________
Python tracker <rep...@bugs.python.org>
<https://bugs.python.org/issue45670>
_______________________________________
_______________________________________________
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com

Reply via email to