Commit 31017aed authored by Raymond Hettinger's avatar Raymond Hettinger

SF #904720: dict.update should take a 2-tuple sequence like dict.__init_

(Championed by Bob Ippolito.)

The update() method for mappings now accepts all the same argument forms
as the dict() constructor.  This includes item lists and/or keyword
arguments.
parent 6c79a518
...@@ -1266,9 +1266,9 @@ arbitrary objects): ...@@ -1266,9 +1266,9 @@ arbitrary objects):
{a copy of \var{a}'s list of (\var{key}, \var{value}) pairs} {a copy of \var{a}'s list of (\var{key}, \var{value}) pairs}
{(3)} {(3)}
\lineiii{\var{a}.keys()}{a copy of \var{a}'s list of keys}{(3)} \lineiii{\var{a}.keys()}{a copy of \var{a}'s list of keys}{(3)}
\lineiii{\var{a}.update(\var{b})} \lineiii{\var{a}.update(\optional{\var{b}})}
{\code{for \var{k} in \var{b}.keys(): \var{a}[\var{k}] = \var{b}[\var{k}]}} {updates (and overwrites) key/value pairs from \var{b}}
{} {(9)}
\lineiii{\var{a}.fromkeys(\var{seq}\optional{, \var{value}})} \lineiii{\var{a}.fromkeys(\var{seq}\optional{, \var{value}})}
{Creates a new dictionary with keys from \var{seq} and values set to \var{value}} {Creates a new dictionary with keys from \var{seq} and values set to \var{value}}
{(7)} {(7)}
...@@ -1338,6 +1338,13 @@ new dictionary. \var{value} defaults to \code{None}. \versionadded{2.3} ...@@ -1338,6 +1338,13 @@ new dictionary. \var{value} defaults to \code{None}. \versionadded{2.3}
value is given and the key is not found. \versionadded{2.3} value is given and the key is not found. \versionadded{2.3}
\end{description} \end{description}
\item[(9)] \function{update()} accepts either another mapping object
or an iterable of key/value pairs (as a tuple or other iterable of
length two). If keyword arguments are specified, the mapping is
then is updated with those key/value pairs:
\samp{d.update(red=1, blue=2)}.
\versionchanged[Allowed the argument to be an iterable of key/value
pairs and allowed keyword arguments]{2.4}
\subsection{File Objects \subsection{File Objects
\label{bltin-file-objects}} \label{bltin-file-objects}}
......
...@@ -134,6 +134,10 @@ language. ...@@ -134,6 +134,10 @@ language.
\begin{itemize} \begin{itemize}
\item The \method{dict.update()} method now accepts the same
argument forms as the \class{dict} constructor. This includes any
mapping, any iterable of key/value pairs, and/or keyword arguments.
\item The string methods, \method{ljust()}, \method{rjust()}, and \item The string methods, \method{ljust()}, \method{rjust()}, and
\method{center()} now take an optional argument for specifying a \method{center()} now take an optional argument for specifying a
fill character other than a space. fill character other than a space.
......
...@@ -4,8 +4,6 @@ class UserDict: ...@@ -4,8 +4,6 @@ class UserDict:
def __init__(self, dict=None, **kwargs): def __init__(self, dict=None, **kwargs):
self.data = {} self.data = {}
if dict is not None: if dict is not None:
if not hasattr(dict,'keys'):
dict = type({})(dict) # make mapping from a sequence
self.update(dict) self.update(dict)
if len(kwargs): if len(kwargs):
self.update(kwargs) self.update(kwargs)
...@@ -39,14 +37,18 @@ class UserDict: ...@@ -39,14 +37,18 @@ class UserDict:
def itervalues(self): return self.data.itervalues() def itervalues(self): return self.data.itervalues()
def values(self): return self.data.values() def values(self): return self.data.values()
def has_key(self, key): return self.data.has_key(key) def has_key(self, key): return self.data.has_key(key)
def update(self, dict): def update(self, dict=None, **kwargs):
if isinstance(dict, UserDict): if dict is None:
pass
elif isinstance(dict, UserDict):
self.data.update(dict.data) self.data.update(dict.data)
elif isinstance(dict, type(self.data)): elif isinstance(dict, type({})) or not hasattr(dict, 'items'):
self.data.update(dict) self.data.update(dict)
else: else:
for k, v in dict.items(): for k, v in dict.items():
self[k] = v self[k] = v
if len(kwargs):
self.data.update(kwargs)
def get(self, key, failobj=None): def get(self, key, failobj=None):
if not self.has_key(key): if not self.has_key(key):
return failobj return failobj
...@@ -136,17 +138,21 @@ class DictMixin: ...@@ -136,17 +138,21 @@ class DictMixin:
raise KeyError, 'container is empty' raise KeyError, 'container is empty'
del self[k] del self[k]
return (k, v) return (k, v)
def update(self, other): def update(self, other=None, **kwargs):
# Make progressively weaker assumptions about "other" # Make progressively weaker assumptions about "other"
if hasattr(other, 'iteritems'): # iteritems saves memory and lookups if other is None:
pass
elif hasattr(other, 'iteritems'): # iteritems saves memory and lookups
for k, v in other.iteritems(): for k, v in other.iteritems():
self[k] = v self[k] = v
elif hasattr(other, '__iter__'): # iter saves memory elif hasattr(other, 'keys'):
for k in other:
self[k] = other[k]
else:
for k in other.keys(): for k in other.keys():
self[k] = other[k] self[k] = other[k]
else:
for k, v in other:
self[k] = v
if kwargs:
self.update(kwargs)
def get(self, key, default=None): def get(self, key, default=None):
try: try:
return self[key] return self[key]
......
...@@ -433,9 +433,6 @@ else: ...@@ -433,9 +433,6 @@ else:
return key.upper() in self.data return key.upper() in self.data
def get(self, key, failobj=None): def get(self, key, failobj=None):
return self.data.get(key.upper(), failobj) return self.data.get(key.upper(), failobj)
def update(self, dict):
for k, v in dict.items():
self[k] = v
def copy(self): def copy(self):
return dict(self) return dict(self)
...@@ -447,9 +444,6 @@ else: ...@@ -447,9 +444,6 @@ else:
def __setitem__(self, key, item): def __setitem__(self, key, item):
putenv(key, item) putenv(key, item)
self.data[key] = item self.data[key] = item
def update(self, dict):
for k, v in dict.items():
self[k] = v
try: try:
unsetenv unsetenv
except NameError: except NameError:
......
...@@ -86,41 +86,41 @@ class CFunctionCalls(unittest.TestCase): ...@@ -86,41 +86,41 @@ class CFunctionCalls(unittest.TestCase):
self.assertRaises(TypeError, {}.keys, x=2, y=2) self.assertRaises(TypeError, {}.keys, x=2, y=2)
def test_oldargs1_0(self): def test_oldargs1_0(self):
self.assertRaises(TypeError, {}.update) self.assertRaises(TypeError, [].count)
def test_oldargs1_1(self): def test_oldargs1_1(self):
{}.update({}) [].count(1)
def test_oldargs1_2(self): def test_oldargs1_2(self):
self.assertRaises(TypeError, {}.update, {}, 1) self.assertRaises(TypeError, [].count, 1, 2)
def test_oldargs1_0_ext(self): def test_oldargs1_0_ext(self):
try: try:
{}.update(*()) [].count(*())
except TypeError: except TypeError:
pass pass
else: else:
raise RuntimeError raise RuntimeError
def test_oldargs1_1_ext(self): def test_oldargs1_1_ext(self):
{}.update(*({},)) [].count(*(1,))
def test_oldargs1_2_ext(self): def test_oldargs1_2_ext(self):
try: try:
{}.update(*({}, 2)) [].count(*(1, 2))
except TypeError: except TypeError:
pass pass
else: else:
raise RuntimeError raise RuntimeError
def test_oldargs1_0_kw(self): def test_oldargs1_0_kw(self):
self.assertRaises(TypeError, {}.update, x=2) self.assertRaises(TypeError, [].count, x=2)
def test_oldargs1_1_kw(self): def test_oldargs1_1_kw(self):
self.assertRaises(TypeError, {}.update, {}, x=2) self.assertRaises(TypeError, [].count, {}, x=2)
def test_oldargs1_2_kw(self): def test_oldargs1_2_kw(self):
self.assertRaises(TypeError, {}.update, x=2, y=2) self.assertRaises(TypeError, [].count, x=2, y=2)
def test_main(): def test_main():
......
...@@ -253,7 +253,7 @@ d.update({1:1, 2:2, 3:3}) ...@@ -253,7 +253,7 @@ d.update({1:1, 2:2, 3:3})
if d != {1:1, 2:2, 3:3}: raise TestFailed, 'dict update' if d != {1:1, 2:2, 3:3}: raise TestFailed, 'dict update'
d.clear() d.clear()
try: d.update(None) try: d.update(None)
except AttributeError: pass except (TypeError, AttributeError): pass
else: raise TestFailed, 'dict.update(None), AttributeError expected' else: raise TestFailed, 'dict.update(None), AttributeError expected'
class SimpleUserDict: class SimpleUserDict:
def __init__(self): def __init__(self):
......
...@@ -93,8 +93,12 @@ class TestMappingProtocol(unittest.TestCase): ...@@ -93,8 +93,12 @@ class TestMappingProtocol(unittest.TestCase):
#update #update
p.update(self.reference) p.update(self.reference)
self.assertEqual(dict(p), self.reference) self.assertEqual(dict(p), self.reference)
items = p.items()
p = self._empty_mapping()
p.update(items)
self.assertEqual(dict(p), self.reference)
d = self._full_mapping(self.reference) d = self._full_mapping(self.reference)
#setdefaullt #setdefault
key, value = d.iteritems().next() key, value = d.iteritems().next()
knownkey, knownvalue = self.other.iteritems().next() knownkey, knownvalue = self.other.iteritems().next()
self.assertEqual(d.setdefault(key, knownvalue), value) self.assertEqual(d.setdefault(key, knownvalue), value)
......
...@@ -122,10 +122,15 @@ class WeakValueDictionary(UserDict.UserDict): ...@@ -122,10 +122,15 @@ class WeakValueDictionary(UserDict.UserDict):
else: else:
return wr() return wr()
def update(self, dict): def update(self, dict=None, **kwargs):
d = self.data d = self.data
for key, o in dict.items(): if dict is not None:
d[key] = ref(o, self.__makeremove(key)) if not hasattr(dict, "items"):
dict = type({})(dict)
for key, o in dict.items():
d[key] = ref(o, self.__makeremove(key))
if len(kwargs):
self.update(kwargs)
def values(self): def values(self):
L = [] L = []
...@@ -239,10 +244,15 @@ class WeakKeyDictionary(UserDict.UserDict): ...@@ -239,10 +244,15 @@ class WeakKeyDictionary(UserDict.UserDict):
def setdefault(self, key, default): def setdefault(self, key, default):
return self.data.setdefault(ref(key, self._remove),default) return self.data.setdefault(ref(key, self._remove),default)
def update(self, dict): def update(self, dict=None, **kwargs):
d = self.data d = self.data
for key, value in dict.items(): if dict is not None:
d[ref(key, self._remove)] = value if not hasattr(dict, "items"):
dict = type({})(dict)
for key, value in dict.items():
d[ref(key, self._remove)] = value
if len(kwargs):
self.update(kwargs)
class BaseIter: class BaseIter:
......
...@@ -273,6 +273,7 @@ Mihai Ibanescu ...@@ -273,6 +273,7 @@ Mihai Ibanescu
Juan David Ibez Palomar Juan David Ibez Palomar
Tony Ingraldi Tony Ingraldi
John Interrante John Interrante
Bob Ippolito
Ben Jackson Ben Jackson
Paul Jackson Paul Jackson
David Jacobs David Jacobs
......
...@@ -32,6 +32,10 @@ Core and builtins ...@@ -32,6 +32,10 @@ Core and builtins
the overallocation is no more than three elements -- this improves space the overallocation is no more than three elements -- this improves space
utilization for applications that have large numbers of small lists. utilization for applications that have large numbers of small lists.
- The dict.update() method now accepts all the same argument forms
as the dict() constructor. This now includes item lists and/or
keyword arguments.
- Support for arbitrary objects supporting the read-only buffer - Support for arbitrary objects supporting the read-only buffer
interface as the co_code field of code objects (something that was interface as the co_code field of code objects (something that was
only possible to create from C code) has been removed. only possible to create from C code) has been removed.
......
...@@ -1029,10 +1029,30 @@ Fail: ...@@ -1029,10 +1029,30 @@ Fail:
return NULL; return NULL;
} }
static int
dict_update_common(PyObject *self, PyObject *args, PyObject *kwds, char *methname)
{
PyObject *arg = NULL;
int result = 0;
if (!PyArg_UnpackTuple(args, methname, 0, 1, &arg))
result = -1;
else if (arg != NULL) {
if (PyObject_HasAttrString(arg, "keys"))
result = PyDict_Merge(self, arg, 1);
else
result = PyDict_MergeFromSeq2(self, arg, 1);
}
if (result == 0 && kwds != NULL)
result = PyDict_Merge(self, kwds, 1);
return result;
}
static PyObject * static PyObject *
dict_update(PyObject *mp, PyObject *other) dict_update(PyObject *self, PyObject *args, PyObject *kwds)
{ {
if (PyDict_Update(mp, other) < 0) if (dict_update_common(self, args, kwds, "update") == -1)
return NULL; return NULL;
Py_INCREF(Py_None); Py_INCREF(Py_None);
return Py_None; return Py_None;
...@@ -1806,7 +1826,7 @@ static PyMethodDef mapp_methods[] = { ...@@ -1806,7 +1826,7 @@ static PyMethodDef mapp_methods[] = {
items__doc__}, items__doc__},
{"values", (PyCFunction)dict_values, METH_NOARGS, {"values", (PyCFunction)dict_values, METH_NOARGS,
values__doc__}, values__doc__},
{"update", (PyCFunction)dict_update, METH_O, {"update", (PyCFunction)dict_update, METH_VARARGS | METH_KEYWORDS,
update__doc__}, update__doc__},
{"fromkeys", (PyCFunction)dict_fromkeys, METH_VARARGS | METH_CLASS, {"fromkeys", (PyCFunction)dict_fromkeys, METH_VARARGS | METH_CLASS,
fromkeys__doc__}, fromkeys__doc__},
...@@ -1875,21 +1895,7 @@ dict_new(PyTypeObject *type, PyObject *args, PyObject *kwds) ...@@ -1875,21 +1895,7 @@ dict_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
static int static int
dict_init(PyObject *self, PyObject *args, PyObject *kwds) dict_init(PyObject *self, PyObject *args, PyObject *kwds)
{ {
PyObject *arg = NULL; return dict_update_common(self, args, kwds, "dict");
int result = 0;
if (!PyArg_UnpackTuple(args, "dict", 0, 1, &arg))
result = -1;
else if (arg != NULL) {
if (PyObject_HasAttrString(arg, "keys"))
result = PyDict_Merge(self, arg, 1);
else
result = PyDict_MergeFromSeq2(self, arg, 1);
}
if (result == 0 && kwds != NULL)
result = PyDict_Merge(self, kwds, 1);
return result;
} }
static long static long
......
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