From 31017aed36a5c5b0e4b16ca58bea09c9ce360134 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Thu, 4 Mar 2004 08:25:44 +0000 Subject: [PATCH] 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. --- Doc/lib/libstdtypes.tex | 13 ++++++++--- Doc/whatsnew/whatsnew24.tex | 4 ++++ Lib/UserDict.py | 28 +++++++++++++---------- Lib/os.py | 6 ----- Lib/test/test_call.py | 18 +++++++-------- Lib/test/test_types.py | 2 +- Lib/test/test_userdict.py | 6 ++++- Lib/weakref.py | 22 ++++++++++++++----- Misc/ACKS | 1 + Misc/NEWS | 4 ++++ Objects/dictobject.c | 44 +++++++++++++++++++++---------------- 11 files changed, 92 insertions(+), 56 deletions(-) diff --git a/Doc/lib/libstdtypes.tex b/Doc/lib/libstdtypes.tex index 7c5cba0dc9e..c39be284b17 100644 --- a/Doc/lib/libstdtypes.tex +++ b/Doc/lib/libstdtypes.tex @@ -1266,9 +1266,9 @@ arbitrary objects): {a copy of \var{a}'s list of (\var{key}, \var{value}) pairs} {(3)} \lineiii{\var{a}.keys()}{a copy of \var{a}'s list of keys}{(3)} - \lineiii{\var{a}.update(\var{b})} - {\code{for \var{k} in \var{b}.keys(): \var{a}[\var{k}] = \var{b}[\var{k}]}} - {} + \lineiii{\var{a}.update(\optional{\var{b}})} + {updates (and overwrites) key/value pairs from \var{b}} + {(9)} \lineiii{\var{a}.fromkeys(\var{seq}\optional{, \var{value}})} {Creates a new dictionary with keys from \var{seq} and values set to \var{value}} {(7)} @@ -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} \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 \label{bltin-file-objects}} diff --git a/Doc/whatsnew/whatsnew24.tex b/Doc/whatsnew/whatsnew24.tex index eb377bec68d..24650fa5bc8 100644 --- a/Doc/whatsnew/whatsnew24.tex +++ b/Doc/whatsnew/whatsnew24.tex @@ -134,6 +134,10 @@ language. \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 \method{center()} now take an optional argument for specifying a fill character other than a space. diff --git a/Lib/UserDict.py b/Lib/UserDict.py index 8141e7f31be..87cc6a35a96 100644 --- a/Lib/UserDict.py +++ b/Lib/UserDict.py @@ -4,8 +4,6 @@ class UserDict: def __init__(self, dict=None, **kwargs): self.data = {} if dict is not None: - if not hasattr(dict,'keys'): - dict = type({})(dict) # make mapping from a sequence self.update(dict) if len(kwargs): self.update(kwargs) @@ -39,14 +37,18 @@ class UserDict: def itervalues(self): return self.data.itervalues() def values(self): return self.data.values() def has_key(self, key): return self.data.has_key(key) - def update(self, dict): - if isinstance(dict, UserDict): + def update(self, dict=None, **kwargs): + if dict is None: + pass + elif isinstance(dict, UserDict): self.data.update(dict.data) - elif isinstance(dict, type(self.data)): + elif isinstance(dict, type({})) or not hasattr(dict, 'items'): self.data.update(dict) else: for k, v in dict.items(): self[k] = v + if len(kwargs): + self.data.update(kwargs) def get(self, key, failobj=None): if not self.has_key(key): return failobj @@ -136,17 +138,21 @@ class DictMixin: raise KeyError, 'container is empty' del self[k] return (k, v) - def update(self, other): + def update(self, other=None, **kwargs): # 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(): self[k] = v - elif hasattr(other, '__iter__'): # iter saves memory - for k in other: - self[k] = other[k] - else: + elif hasattr(other, 'keys'): for k in other.keys(): self[k] = other[k] + else: + for k, v in other: + self[k] = v + if kwargs: + self.update(kwargs) def get(self, key, default=None): try: return self[key] diff --git a/Lib/os.py b/Lib/os.py index 8cec9125ad7..fdb9a4604ad 100644 --- a/Lib/os.py +++ b/Lib/os.py @@ -433,9 +433,6 @@ else: return key.upper() in self.data def get(self, key, failobj=None): return self.data.get(key.upper(), failobj) - def update(self, dict): - for k, v in dict.items(): - self[k] = v def copy(self): return dict(self) @@ -447,9 +444,6 @@ else: def __setitem__(self, key, item): putenv(key, item) self.data[key] = item - def update(self, dict): - for k, v in dict.items(): - self[k] = v try: unsetenv except NameError: diff --git a/Lib/test/test_call.py b/Lib/test/test_call.py index 99554c7b9cf..f3c7c8c394f 100644 --- a/Lib/test/test_call.py +++ b/Lib/test/test_call.py @@ -86,41 +86,41 @@ class CFunctionCalls(unittest.TestCase): self.assertRaises(TypeError, {}.keys, x=2, y=2) def test_oldargs1_0(self): - self.assertRaises(TypeError, {}.update) + self.assertRaises(TypeError, [].count) def test_oldargs1_1(self): - {}.update({}) + [].count(1) def test_oldargs1_2(self): - self.assertRaises(TypeError, {}.update, {}, 1) + self.assertRaises(TypeError, [].count, 1, 2) def test_oldargs1_0_ext(self): try: - {}.update(*()) + [].count(*()) except TypeError: pass else: raise RuntimeError def test_oldargs1_1_ext(self): - {}.update(*({},)) + [].count(*(1,)) def test_oldargs1_2_ext(self): try: - {}.update(*({}, 2)) + [].count(*(1, 2)) except TypeError: pass else: raise RuntimeError def test_oldargs1_0_kw(self): - self.assertRaises(TypeError, {}.update, x=2) + self.assertRaises(TypeError, [].count, x=2) def test_oldargs1_1_kw(self): - self.assertRaises(TypeError, {}.update, {}, x=2) + self.assertRaises(TypeError, [].count, {}, x=2) 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(): diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index aa8f854db02..4b4e19cd768 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -253,7 +253,7 @@ d.update({1:1, 2:2, 3:3}) if d != {1:1, 2:2, 3:3}: raise TestFailed, 'dict update' d.clear() try: d.update(None) -except AttributeError: pass +except (TypeError, AttributeError): pass else: raise TestFailed, 'dict.update(None), AttributeError expected' class SimpleUserDict: def __init__(self): diff --git a/Lib/test/test_userdict.py b/Lib/test/test_userdict.py index 602fd90551b..0ed436998c8 100644 --- a/Lib/test/test_userdict.py +++ b/Lib/test/test_userdict.py @@ -93,8 +93,12 @@ class TestMappingProtocol(unittest.TestCase): #update p.update(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) - #setdefaullt + #setdefault key, value = d.iteritems().next() knownkey, knownvalue = self.other.iteritems().next() self.assertEqual(d.setdefault(key, knownvalue), value) diff --git a/Lib/weakref.py b/Lib/weakref.py index 09bed6560e6..5c661861b03 100644 --- a/Lib/weakref.py +++ b/Lib/weakref.py @@ -122,10 +122,15 @@ class WeakValueDictionary(UserDict.UserDict): else: return wr() - def update(self, dict): + def update(self, dict=None, **kwargs): d = self.data - for key, o in dict.items(): - d[key] = ref(o, self.__makeremove(key)) + if dict is not None: + 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): L = [] @@ -239,10 +244,15 @@ class WeakKeyDictionary(UserDict.UserDict): def setdefault(self, key, default): return self.data.setdefault(ref(key, self._remove),default) - def update(self, dict): + def update(self, dict=None, **kwargs): d = self.data - for key, value in dict.items(): - d[ref(key, self._remove)] = value + if dict is not None: + 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: diff --git a/Misc/ACKS b/Misc/ACKS index 61b07cba53e..befc46bfab0 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -273,6 +273,7 @@ Mihai Ibanescu Juan David Ibáñez Palomar Tony Ingraldi John Interrante +Bob Ippolito Ben Jackson Paul Jackson David Jacobs diff --git a/Misc/NEWS b/Misc/NEWS index 1bd6c2b7f87..72232c8de85 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -32,6 +32,10 @@ Core and builtins the overallocation is no more than three elements -- this improves space 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 interface as the co_code field of code objects (something that was only possible to create from C code) has been removed. diff --git a/Objects/dictobject.c b/Objects/dictobject.c index eb0222cdd56..b5cbd667d94 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -1029,10 +1029,30 @@ Fail: return NULL; } -static PyObject * -dict_update(PyObject *mp, PyObject *other) +static int +dict_update_common(PyObject *self, PyObject *args, PyObject *kwds, char *methname) { - if (PyDict_Update(mp, other) < 0) + 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 * +dict_update(PyObject *self, PyObject *args, PyObject *kwds) +{ + if (dict_update_common(self, args, kwds, "update") == -1) return NULL; Py_INCREF(Py_None); return Py_None; @@ -1806,7 +1826,7 @@ static PyMethodDef mapp_methods[] = { items__doc__}, {"values", (PyCFunction)dict_values, METH_NOARGS, values__doc__}, - {"update", (PyCFunction)dict_update, METH_O, + {"update", (PyCFunction)dict_update, METH_VARARGS | METH_KEYWORDS, update__doc__}, {"fromkeys", (PyCFunction)dict_fromkeys, METH_VARARGS | METH_CLASS, fromkeys__doc__}, @@ -1875,21 +1895,7 @@ dict_new(PyTypeObject *type, PyObject *args, PyObject *kwds) static int dict_init(PyObject *self, PyObject *args, PyObject *kwds) { - PyObject *arg = NULL; - 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; + return dict_update_common(self, args, kwds, "dict"); } static long