Commit 0db176f8 authored by Victor Stinner's avatar Victor Stinner

Issue #14386: Expose the dict_proxy internal type as types.MappingProxyType

parent 8a1d04c6
......@@ -36,11 +36,11 @@ Dictionary Objects
Return a new empty dictionary, or *NULL* on failure.
.. c:function:: PyObject* PyDictProxy_New(PyObject *dict)
.. c:function:: PyObject* PyDictProxy_New(PyObject *mapping)
Return a proxy object for a mapping which enforces read-only behavior.
This is normally used to create a proxy to prevent modification of the
dictionary for non-dynamic class types.
Return a :class:`types.MappingProxyType` object for a mapping which
enforces read-only behavior. This is normally used to create a view to
prevent modification of the dictionary for non-dynamic class types.
.. c:function:: void PyDict_Clear(PyObject *p)
......
......@@ -2258,13 +2258,13 @@ pairs within braces, for example: ``{'jack': 4098, 'sjoerd': 4127}`` or ``{4098:
.. method:: items()
Return a new view of the dictionary's items (``(key, value)`` pairs). See
below for documentation of view objects.
Return a new view of the dictionary's items (``(key, value)`` pairs).
See the :ref:`documentation of view objects <dict-views>`.
.. method:: keys()
Return a new view of the dictionary's keys. See below for documentation of
view objects.
Return a new view of the dictionary's keys. See the :ref:`documentation
of view objects <dict-views>`.
.. method:: pop(key[, default])
......@@ -2298,8 +2298,12 @@ pairs within braces, for example: ``{'jack': 4098, 'sjoerd': 4127}`` or ``{4098:
.. method:: values()
Return a new view of the dictionary's values. See below for documentation of
view objects.
Return a new view of the dictionary's values. See the
:ref:`documentation of view objects <dict-views>`.
.. seealso::
:class:`types.MappingProxyType` can be used to create a read-only view
of a :class:`dict`.
.. _dict-views:
......
......@@ -85,3 +85,55 @@ The module defines the following names:
In other implementations of Python, this type may be identical to
``GetSetDescriptorType``.
.. class:: MappingProxyType(mapping)
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.
.. versionadded:: 3.3
.. describe:: key in proxy
Return ``True`` if the underlying mapping has a key *key*, else
``False``.
.. describe:: proxy[key]
Return the item of the underlying mapping with key *key*. Raises a
:exc:`KeyError` if *key* is not in the underlying mapping.
.. describe:: iter(proxy)
Return an iterator over the keys of the underlying mapping. This is a
shortcut for ``iter(proxy.keys())``.
.. describe:: len(proxy)
Return the number of items in the underlying mapping.
.. method:: copy()
Return a shallow copy of the underlying mapping.
.. method:: get(key[, default])
Return the value for *key* if *key* is in the underlying mapping, else
*default*. If *default* is not given, it defaults to ``None``, so that
this method never raises a :exc:`KeyError`.
.. method:: items()
Return a new view of the underlying mapping's items (``(key, value)``
pairs).
.. method:: keys()
Return a new view of the underlying mapping's keys.
.. method:: values()
Return a new view of the underlying mapping's values.
......@@ -1068,6 +1068,13 @@ The :mod:`time` module has new functions:
(Contributed by Victor Stinner in :issue:`10278`)
types
-----
Add a new :class:`types.MappingProxyType` class: Read-only proxy of a mapping.
(:issue:`14386`)
urllib
------
......
......@@ -4574,11 +4574,11 @@ class DictProxyTests(unittest.TestCase):
self.assertEqual(type(C.__dict__), type(B.__dict__))
def test_repr(self):
# Testing dict_proxy.__repr__.
# Testing mappingproxy.__repr__.
# We can't blindly compare with the repr of another dict as ordering
# of keys and values is arbitrary and may differ.
r = repr(self.C.__dict__)
self.assertTrue(r.startswith('dict_proxy('), r)
self.assertTrue(r.startswith('mappingproxy('), r)
self.assertTrue(r.endswith(')'), r)
for k, v in self.C.__dict__.items():
self.assertIn('{!r}: {!r}'.format(k, v), r)
......
# Python test set -- part 6, built-in types
from test.support import run_unittest, run_with_locale
import unittest
import sys
import collections
import locale
import sys
import types
import unittest
class TypesTests(unittest.TestCase):
......@@ -569,8 +571,184 @@ class TypesTests(unittest.TestCase):
self.assertGreater(tuple.__itemsize__, 0)
class MappingProxyTests(unittest.TestCase):
mappingproxy = types.MappingProxyType
def test_constructor(self):
class userdict(dict):
pass
mapping = {'x': 1, 'y': 2}
self.assertEqual(self.mappingproxy(mapping), mapping)
mapping = userdict(x=1, y=2)
self.assertEqual(self.mappingproxy(mapping), mapping)
mapping = collections.ChainMap({'x': 1}, {'y': 2})
self.assertEqual(self.mappingproxy(mapping), mapping)
self.assertRaises(TypeError, self.mappingproxy, 10)
self.assertRaises(TypeError, self.mappingproxy, ("a", "tuple"))
self.assertRaises(TypeError, self.mappingproxy, ["a", "list"])
def test_methods(self):
attrs = set(dir(self.mappingproxy({}))) - set(dir(object()))
self.assertEqual(attrs, {
'__contains__',
'__getitem__',
'__iter__',
'__len__',
'copy',
'get',
'items',
'keys',
'values',
})
def test_get(self):
view = self.mappingproxy({'a': 'A', 'b': 'B'})
self.assertEqual(view['a'], 'A')
self.assertEqual(view['b'], 'B')
self.assertRaises(KeyError, view.__getitem__, 'xxx')
self.assertEqual(view.get('a'), 'A')
self.assertIsNone(view.get('xxx'))
self.assertEqual(view.get('xxx', 42), 42)
def test_missing(self):
class dictmissing(dict):
def __missing__(self, key):
return "missing=%s" % key
view = self.mappingproxy(dictmissing(x=1))
self.assertEqual(view['x'], 1)
self.assertEqual(view['y'], 'missing=y')
self.assertEqual(view.get('x'), 1)
self.assertEqual(view.get('y'), None)
self.assertEqual(view.get('y', 42), 42)
self.assertTrue('x' in view)
self.assertFalse('y' in view)
def test_customdict(self):
class customdict(dict):
def __contains__(self, key):
if key == 'magic':
return True
else:
return dict.__contains__(self, key)
def __iter__(self):
return iter(('iter',))
def __len__(self):
return 500
def copy(self):
return 'copy'
def keys(self):
return 'keys'
def items(self):
return 'items'
def values(self):
return 'values'
def __getitem__(self, key):
return "getitem=%s" % dict.__getitem__(self, key)
def get(self, key, default=None):
return "get=%s" % dict.get(self, key, 'default=%r' % default)
custom = customdict({'key': 'value'})
view = self.mappingproxy(custom)
self.assertTrue('key' in view)
self.assertTrue('magic' in view)
self.assertFalse('xxx' in view)
self.assertEqual(view['key'], 'getitem=value')
self.assertRaises(KeyError, view.__getitem__, 'xxx')
self.assertEqual(tuple(view), ('iter',))
self.assertEqual(len(view), 500)
self.assertEqual(view.copy(), 'copy')
self.assertEqual(view.get('key'), 'get=value')
self.assertEqual(view.get('xxx'), 'get=default=None')
self.assertEqual(view.items(), 'items')
self.assertEqual(view.keys(), 'keys')
self.assertEqual(view.values(), 'values')
def test_chainmap(self):
d1 = {'x': 1}
d2 = {'y': 2}
mapping = collections.ChainMap(d1, d2)
view = self.mappingproxy(mapping)
self.assertTrue('x' in view)
self.assertTrue('y' in view)
self.assertFalse('z' in view)
self.assertEqual(view['x'], 1)
self.assertEqual(view['y'], 2)
self.assertRaises(KeyError, view.__getitem__, 'z')
self.assertEqual(tuple(sorted(view)), ('x', 'y'))
self.assertEqual(len(view), 2)
copy = view.copy()
self.assertIsNot(copy, mapping)
self.assertIsInstance(copy, collections.ChainMap)
self.assertEqual(copy, mapping)
self.assertEqual(view.get('x'), 1)
self.assertEqual(view.get('y'), 2)
self.assertIsNone(view.get('z'))
self.assertEqual(tuple(sorted(view.items())), (('x', 1), ('y', 2)))
self.assertEqual(tuple(sorted(view.keys())), ('x', 'y'))
self.assertEqual(tuple(sorted(view.values())), (1, 2))
def test_contains(self):
view = self.mappingproxy(dict.fromkeys('abc'))
self.assertTrue('a' in view)
self.assertTrue('b' in view)
self.assertTrue('c' in view)
self.assertFalse('xxx' in view)
def test_views(self):
mapping = {}
view = self.mappingproxy(mapping)
keys = view.keys()
values = view.values()
items = view.items()
self.assertEqual(list(keys), [])
self.assertEqual(list(values), [])
self.assertEqual(list(items), [])
mapping['key'] = 'value'
self.assertEqual(list(keys), ['key'])
self.assertEqual(list(values), ['value'])
self.assertEqual(list(items), [('key', 'value')])
def test_len(self):
for expected in range(6):
data = dict.fromkeys('abcde'[:expected])
self.assertEqual(len(data), expected)
view = self.mappingproxy(data)
self.assertEqual(len(view), expected)
def test_iterators(self):
keys = ('x', 'y')
values = (1, 2)
items = tuple(zip(keys, values))
view = self.mappingproxy(dict(items))
self.assertEqual(set(view), set(keys))
self.assertEqual(set(view.keys()), set(keys))
self.assertEqual(set(view.values()), set(values))
self.assertEqual(set(view.items()), set(items))
def test_copy(self):
original = {'key1': 27, 'key2': 51, 'key3': 93}
view = self.mappingproxy(original)
copy = view.copy()
self.assertEqual(type(copy), dict)
self.assertEqual(copy, original)
original['key1'] = 70
self.assertEqual(view['key1'], 70)
self.assertEqual(copy['key1'], 27)
def test_main():
run_unittest(TypesTests)
run_unittest(TypesTests, MappingProxyTests)
if __name__ == '__main__':
test_main()
......@@ -12,6 +12,7 @@ def _f(): pass
FunctionType = type(_f)
LambdaType = type(lambda: None) # Same as FunctionType
CodeType = type(_f.__code__)
MappingProxyType = type(type.__dict__)
def _g():
yield 1
......
......@@ -32,6 +32,8 @@ Core and Builtins
Library
-------
- Issue #14386: Expose the dict_proxy internal type as types.MappingProxyType.
- Issue #13959: Make imp.reload() always use a module's __loader__ to perform
the reload.
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment