Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 0db176f

Browse files
committed
Issue #14386: Expose the dict_proxy internal type as types.MappingProxyType
1 parent 8a1d04c commit 0db176f

9 files changed

Lines changed: 369 additions & 75 deletions

File tree

Doc/c-api/dict.rst

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,11 @@ Dictionary Objects
3636
Return a new empty dictionary, or *NULL* on failure.
3737
3838
39-
.. c:function:: PyObject* PyDictProxy_New(PyObject *dict)
39+
.. c:function:: PyObject* PyDictProxy_New(PyObject *mapping)
4040
41-
Return a proxy object for a mapping which enforces read-only behavior.
42-
This is normally used to create a proxy to prevent modification of the
43-
dictionary for non-dynamic class types.
41+
Return a :class:`types.MappingProxyType` object for a mapping which
42+
enforces read-only behavior. This is normally used to create a view to
43+
prevent modification of the dictionary for non-dynamic class types.
4444
4545
4646
.. c:function:: void PyDict_Clear(PyObject *p)

Doc/library/stdtypes.rst

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2258,13 +2258,13 @@ pairs within braces, for example: ``{'jack': 4098, 'sjoerd': 4127}`` or ``{4098:
22582258

22592259
.. method:: items()
22602260

2261-
Return a new view of the dictionary's items (``(key, value)`` pairs). See
2262-
below for documentation of view objects.
2261+
Return a new view of the dictionary's items (``(key, value)`` pairs).
2262+
See the :ref:`documentation of view objects <dict-views>`.
22632263

22642264
.. method:: keys()
22652265

2266-
Return a new view of the dictionary's keys. See below for documentation of
2267-
view objects.
2266+
Return a new view of the dictionary's keys. See the :ref:`documentation
2267+
of view objects <dict-views>`.
22682268

22692269
.. method:: pop(key[, default])
22702270

@@ -2298,8 +2298,12 @@ pairs within braces, for example: ``{'jack': 4098, 'sjoerd': 4127}`` or ``{4098:
22982298

22992299
.. method:: values()
23002300

2301-
Return a new view of the dictionary's values. See below for documentation of
2302-
view objects.
2301+
Return a new view of the dictionary's values. See the
2302+
:ref:`documentation of view objects <dict-views>`.
2303+
2304+
.. seealso::
2305+
:class:`types.MappingProxyType` can be used to create a read-only view
2306+
of a :class:`dict`.
23032307

23042308

23052309
.. _dict-views:

Doc/library/types.rst

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,55 @@ The module defines the following names:
8585

8686
In other implementations of Python, this type may be identical to
8787
``GetSetDescriptorType``.
88+
89+
.. class:: MappingProxyType(mapping)
90+
91+
Read-only proxy of a mapping. It provides a dynamic view on the mapping's
92+
entries, which means that when the mapping changes, the view reflects these
93+
changes.
94+
95+
.. versionadded:: 3.3
96+
97+
.. describe:: key in proxy
98+
99+
Return ``True`` if the underlying mapping has a key *key*, else
100+
``False``.
101+
102+
.. describe:: proxy[key]
103+
104+
Return the item of the underlying mapping with key *key*. Raises a
105+
:exc:`KeyError` if *key* is not in the underlying mapping.
106+
107+
.. describe:: iter(proxy)
108+
109+
Return an iterator over the keys of the underlying mapping. This is a
110+
shortcut for ``iter(proxy.keys())``.
111+
112+
.. describe:: len(proxy)
113+
114+
Return the number of items in the underlying mapping.
115+
116+
.. method:: copy()
117+
118+
Return a shallow copy of the underlying mapping.
119+
120+
.. method:: get(key[, default])
121+
122+
Return the value for *key* if *key* is in the underlying mapping, else
123+
*default*. If *default* is not given, it defaults to ``None``, so that
124+
this method never raises a :exc:`KeyError`.
125+
126+
.. method:: items()
127+
128+
Return a new view of the underlying mapping's items (``(key, value)``
129+
pairs).
130+
131+
.. method:: keys()
132+
133+
Return a new view of the underlying mapping's keys.
134+
135+
.. method:: values()
136+
137+
Return a new view of the underlying mapping's values.
138+
139+

Doc/whatsnew/3.3.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1068,6 +1068,13 @@ The :mod:`time` module has new functions:
10681068
(Contributed by Victor Stinner in :issue:`10278`)
10691069

10701070

1071+
types
1072+
-----
1073+
1074+
Add a new :class:`types.MappingProxyType` class: Read-only proxy of a mapping.
1075+
(:issue:`14386`)
1076+
1077+
10711078
urllib
10721079
------
10731080

Lib/test/test_descr.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4574,11 +4574,11 @@ class C(metaclass=M):
45744574
self.assertEqual(type(C.__dict__), type(B.__dict__))
45754575

45764576
def test_repr(self):
4577-
# Testing dict_proxy.__repr__.
4577+
# Testing mappingproxy.__repr__.
45784578
# We can't blindly compare with the repr of another dict as ordering
45794579
# of keys and values is arbitrary and may differ.
45804580
r = repr(self.C.__dict__)
4581-
self.assertTrue(r.startswith('dict_proxy('), r)
4581+
self.assertTrue(r.startswith('mappingproxy('), r)
45824582
self.assertTrue(r.endswith(')'), r)
45834583
for k, v in self.C.__dict__.items():
45844584
self.assertIn('{!r}: {!r}'.format(k, v), r)

Lib/test/test_types.py

Lines changed: 181 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
# Python test set -- part 6, built-in types
22

33
from test.support import run_unittest, run_with_locale
4-
import unittest
5-
import sys
4+
import collections
65
import locale
6+
import sys
7+
import types
8+
import unittest
79

810
class TypesTests(unittest.TestCase):
911

@@ -569,8 +571,184 @@ def test_internal_sizes(self):
569571
self.assertGreater(tuple.__itemsize__, 0)
570572

571573

574+
class MappingProxyTests(unittest.TestCase):
575+
mappingproxy = types.MappingProxyType
576+
577+
def test_constructor(self):
578+
class userdict(dict):
579+
pass
580+
581+
mapping = {'x': 1, 'y': 2}
582+
self.assertEqual(self.mappingproxy(mapping), mapping)
583+
mapping = userdict(x=1, y=2)
584+
self.assertEqual(self.mappingproxy(mapping), mapping)
585+
mapping = collections.ChainMap({'x': 1}, {'y': 2})
586+
self.assertEqual(self.mappingproxy(mapping), mapping)
587+
588+
self.assertRaises(TypeError, self.mappingproxy, 10)
589+
self.assertRaises(TypeError, self.mappingproxy, ("a", "tuple"))
590+
self.assertRaises(TypeError, self.mappingproxy, ["a", "list"])
591+
592+
def test_methods(self):
593+
attrs = set(dir(self.mappingproxy({}))) - set(dir(object()))
594+
self.assertEqual(attrs, {
595+
'__contains__',
596+
'__getitem__',
597+
'__iter__',
598+
'__len__',
599+
'copy',
600+
'get',
601+
'items',
602+
'keys',
603+
'values',
604+
})
605+
606+
def test_get(self):
607+
view = self.mappingproxy({'a': 'A', 'b': 'B'})
608+
self.assertEqual(view['a'], 'A')
609+
self.assertEqual(view['b'], 'B')
610+
self.assertRaises(KeyError, view.__getitem__, 'xxx')
611+
self.assertEqual(view.get('a'), 'A')
612+
self.assertIsNone(view.get('xxx'))
613+
self.assertEqual(view.get('xxx', 42), 42)
614+
615+
def test_missing(self):
616+
class dictmissing(dict):
617+
def __missing__(self, key):
618+
return "missing=%s" % key
619+
620+
view = self.mappingproxy(dictmissing(x=1))
621+
self.assertEqual(view['x'], 1)
622+
self.assertEqual(view['y'], 'missing=y')
623+
self.assertEqual(view.get('x'), 1)
624+
self.assertEqual(view.get('y'), None)
625+
self.assertEqual(view.get('y', 42), 42)
626+
self.assertTrue('x' in view)
627+
self.assertFalse('y' in view)
628+
629+
def test_customdict(self):
630+
class customdict(dict):
631+
def __contains__(self, key):
632+
if key == 'magic':
633+
return True
634+
else:
635+
return dict.__contains__(self, key)
636+
637+
def __iter__(self):
638+
return iter(('iter',))
639+
640+
def __len__(self):
641+
return 500
642+
643+
def copy(self):
644+
return 'copy'
645+
646+
def keys(self):
647+
return 'keys'
648+
649+
def items(self):
650+
return 'items'
651+
652+
def values(self):
653+
return 'values'
654+
655+
def __getitem__(self, key):
656+
return "getitem=%s" % dict.__getitem__(self, key)
657+
658+
def get(self, key, default=None):
659+
return "get=%s" % dict.get(self, key, 'default=%r' % default)
660+
661+
custom = customdict({'key': 'value'})
662+
view = self.mappingproxy(custom)
663+
self.assertTrue('key' in view)
664+
self.assertTrue('magic' in view)
665+
self.assertFalse('xxx' in view)
666+
self.assertEqual(view['key'], 'getitem=value')
667+
self.assertRaises(KeyError, view.__getitem__, 'xxx')
668+
self.assertEqual(tuple(view), ('iter',))
669+
self.assertEqual(len(view), 500)
670+
self.assertEqual(view.copy(), 'copy')
671+
self.assertEqual(view.get('key'), 'get=value')
672+
self.assertEqual(view.get('xxx'), 'get=default=None')
673+
self.assertEqual(view.items(), 'items')
674+
self.assertEqual(view.keys(), 'keys')
675+
self.assertEqual(view.values(), 'values')
676+
677+
def test_chainmap(self):
678+
d1 = {'x': 1}
679+
d2 = {'y': 2}
680+
mapping = collections.ChainMap(d1, d2)
681+
view = self.mappingproxy(mapping)
682+
self.assertTrue('x' in view)
683+
self.assertTrue('y' in view)
684+
self.assertFalse('z' in view)
685+
self.assertEqual(view['x'], 1)
686+
self.assertEqual(view['y'], 2)
687+
self.assertRaises(KeyError, view.__getitem__, 'z')
688+
self.assertEqual(tuple(sorted(view)), ('x', 'y'))
689+
self.assertEqual(len(view), 2)
690+
copy = view.copy()
691+
self.assertIsNot(copy, mapping)
692+
self.assertIsInstance(copy, collections.ChainMap)
693+
self.assertEqual(copy, mapping)
694+
self.assertEqual(view.get('x'), 1)
695+
self.assertEqual(view.get('y'), 2)
696+
self.assertIsNone(view.get('z'))
697+
self.assertEqual(tuple(sorted(view.items())), (('x', 1), ('y', 2)))
698+
self.assertEqual(tuple(sorted(view.keys())), ('x', 'y'))
699+
self.assertEqual(tuple(sorted(view.values())), (1, 2))
700+
701+
def test_contains(self):
702+
view = self.mappingproxy(dict.fromkeys('abc'))
703+
self.assertTrue('a' in view)
704+
self.assertTrue('b' in view)
705+
self.assertTrue('c' in view)
706+
self.assertFalse('xxx' in view)
707+
708+
def test_views(self):
709+
mapping = {}
710+
view = self.mappingproxy(mapping)
711+
keys = view.keys()
712+
values = view.values()
713+
items = view.items()
714+
self.assertEqual(list(keys), [])
715+
self.assertEqual(list(values), [])
716+
self.assertEqual(list(items), [])
717+
mapping['key'] = 'value'
718+
self.assertEqual(list(keys), ['key'])
719+
self.assertEqual(list(values), ['value'])
720+
self.assertEqual(list(items), [('key', 'value')])
721+
722+
def test_len(self):
723+
for expected in range(6):
724+
data = dict.fromkeys('abcde'[:expected])
725+
self.assertEqual(len(data), expected)
726+
view = self.mappingproxy(data)
727+
self.assertEqual(len(view), expected)
728+
729+
def test_iterators(self):
730+
keys = ('x', 'y')
731+
values = (1, 2)
732+
items = tuple(zip(keys, values))
733+
view = self.mappingproxy(dict(items))
734+
self.assertEqual(set(view), set(keys))
735+
self.assertEqual(set(view.keys()), set(keys))
736+
self.assertEqual(set(view.values()), set(values))
737+
self.assertEqual(set(view.items()), set(items))
738+
739+
def test_copy(self):
740+
original = {'key1': 27, 'key2': 51, 'key3': 93}
741+
view = self.mappingproxy(original)
742+
copy = view.copy()
743+
self.assertEqual(type(copy), dict)
744+
self.assertEqual(copy, original)
745+
original['key1'] = 70
746+
self.assertEqual(view['key1'], 70)
747+
self.assertEqual(copy['key1'], 27)
748+
749+
572750
def test_main():
573-
run_unittest(TypesTests)
751+
run_unittest(TypesTests, MappingProxyTests)
574752

575753
if __name__ == '__main__':
576754
test_main()

Lib/types.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ def _f(): pass
1212
FunctionType = type(_f)
1313
LambdaType = type(lambda: None) # Same as FunctionType
1414
CodeType = type(_f.__code__)
15+
MappingProxyType = type(type.__dict__)
1516

1617
def _g():
1718
yield 1

Misc/NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ Core and Builtins
3232
Library
3333
-------
3434

35+
- Issue #14386: Expose the dict_proxy internal type as types.MappingProxyType.
36+
3537
- Issue #13959: Make imp.reload() always use a module's __loader__ to perform
3638
the reload.
3739

0 commit comments

Comments
 (0)