[Python-checkins] cpython (merge 3.6 -> default): Issue #27358: Optimized merging var-keyword arguments and improved error

serhiy.storchaka python-checkins at python.org
Sun Oct 2 04:13:25 EDT 2016


https://hg.python.org/cpython/rev/489ad68b35c0
changeset:   104234:489ad68b35c0
parent:      104232:818e5416ab39
parent:      104233:148172f75d43
user:        Serhiy Storchaka <storchaka at gmail.com>
date:        Sun Oct 02 11:07:29 2016 +0300
summary:
  Issue #27358: Optimized merging var-keyword arguments and improved error
message when pass a non-mapping as a var-keyword argument.

files:
  Include/dictobject.h     |    6 +
  Lib/test/test_extcall.py |   25 ++++++
  Misc/NEWS                |    3 +
  Objects/dictobject.c     |   45 +++++++++--
  Python/ceval.c           |  102 ++++++++++++++------------
  5 files changed, 126 insertions(+), 55 deletions(-)


diff --git a/Include/dictobject.h b/Include/dictobject.h
--- a/Include/dictobject.h
+++ b/Include/dictobject.h
@@ -132,6 +132,12 @@
                                    int override);
 
 #ifndef Py_LIMITED_API
+/* Like PyDict_Merge, but override can be 0, 1 or 2.  If override is 0,
+   the first occurrence of a key wins, if override is 1, the last occurrence
+   of a key wins, if override is 2, a KeyError with conflicting key as
+   argument is raised.
+*/
+PyAPI_FUNC(int) _PyDict_MergeEx(PyObject *mp, PyObject *other, int override);
 PyAPI_FUNC(PyObject *) _PyDictView_Intersect(PyObject* self, PyObject *other);
 #endif
 
diff --git a/Lib/test/test_extcall.py b/Lib/test/test_extcall.py
--- a/Lib/test/test_extcall.py
+++ b/Lib/test/test_extcall.py
@@ -259,6 +259,31 @@
       ...
     TypeError: h() argument after ** must be a mapping, not function
 
+    >>> h(**[])
+    Traceback (most recent call last):
+      ...
+    TypeError: h() argument after ** must be a mapping, not list
+
+    >>> h(a=1, **h)
+    Traceback (most recent call last):
+      ...
+    TypeError: h() argument after ** must be a mapping, not function
+
+    >>> h(a=1, **[])
+    Traceback (most recent call last):
+      ...
+    TypeError: h() argument after ** must be a mapping, not list
+
+    >>> h(**{'a': 1}, **h)
+    Traceback (most recent call last):
+      ...
+    TypeError: h() argument after ** must be a mapping, not function
+
+    >>> h(**{'a': 1}, **[])
+    Traceback (most recent call last):
+      ...
+    TypeError: h() argument after ** must be a mapping, not list
+
     >>> dir(**h)
     Traceback (most recent call last):
       ...
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -54,6 +54,9 @@
 Library
 -------
 
+- Issue #27358: Optimized merging var-keyword arguments and improved error
+  message when pass a non-mapping as a var-keyword argument.
+
 - Issue #28257: Improved error message when pass a non-iterable as
   a var-positional argument.  Added opcode BUILD_TUPLE_UNPACK_WITH_CALL.
 
diff --git a/Objects/dictobject.c b/Objects/dictobject.c
--- a/Objects/dictobject.c
+++ b/Objects/dictobject.c
@@ -2380,18 +2380,14 @@
 }
 
 int
-PyDict_Update(PyObject *a, PyObject *b)
-{
-    return PyDict_Merge(a, b, 1);
-}
-
-int
-PyDict_Merge(PyObject *a, PyObject *b, int override)
+dict_merge(PyObject *a, PyObject *b, int override)
 {
     PyDictObject *mp, *other;
     Py_ssize_t i, n;
     PyDictKeyEntry *entry, *ep0;
 
+    assert(0 <= override && override <= 2);
+
     /* We accept for the argument either a concrete dictionary object,
      * or an abstract "mapping" object.  For the former, we can do
      * things quite efficiently.  For the latter, we only require that
@@ -2436,8 +2432,14 @@
                 int err = 0;
                 Py_INCREF(key);
                 Py_INCREF(value);
-                if (override || PyDict_GetItem(a, key) == NULL)
+                if (override == 1 || _PyDict_GetItem_KnownHash(a, key, hash) == NULL)
                     err = insertdict(mp, key, hash, value);
+                else if (override != 0) {
+                    _PyErr_SetKeyError(key);
+                    Py_DECREF(value);
+                    Py_DECREF(key);
+                    return -1;
+                }
                 Py_DECREF(value);
                 Py_DECREF(key);
                 if (err != 0)
@@ -2472,7 +2474,13 @@
             return -1;
 
         for (key = PyIter_Next(iter); key; key = PyIter_Next(iter)) {
-            if (!override && PyDict_GetItem(a, key) != NULL) {
+            if (override != 1 && PyDict_GetItem(a, key) != NULL) {
+                if (override != 0) {
+                    _PyErr_SetKeyError(key);
+                    Py_DECREF(key);
+                    Py_DECREF(iter);
+                    return -1;
+                }
                 Py_DECREF(key);
                 continue;
             }
@@ -2499,6 +2507,25 @@
     return 0;
 }
 
+int
+PyDict_Update(PyObject *a, PyObject *b)
+{
+    return dict_merge(a, b, 1);
+}
+
+int
+PyDict_Merge(PyObject *a, PyObject *b, int override)
+{
+    /* XXX Deprecate override not in (0, 1). */
+    return dict_merge(a, b, override != 0);
+}
+
+int
+_PyDict_MergeEx(PyObject *a, PyObject *b, int override)
+{
+    return dict_merge(a, b, override);
+}
+
 static PyObject *
 dict_copy(PyDictObject *mp)
 {
diff --git a/Python/ceval.c b/Python/ceval.c
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -2710,9 +2710,7 @@
             DISPATCH();
         }
 
-        TARGET(BUILD_MAP_UNPACK_WITH_CALL)
         TARGET(BUILD_MAP_UNPACK) {
-            int with_call = opcode == BUILD_MAP_UNPACK_WITH_CALL;
             Py_ssize_t i;
             PyObject *sum = PyDict_New();
             if (sum == NULL)
@@ -2720,53 +2718,10 @@
 
             for (i = oparg; i > 0; i--) {
                 PyObject *arg = PEEK(i);
-                if (with_call && PyDict_Size(sum)) {
-                    PyObject *intersection = _PyDictView_Intersect(sum, arg);
-
-                    if (intersection == NULL) {
-                        if (PyErr_ExceptionMatches(PyExc_AttributeError)) {
-                            PyObject *func = PEEK(2 + oparg);
-                            PyErr_Format(PyExc_TypeError,
-                                    "%.200s%.200s argument after ** "
-                                    "must be a mapping, not %.200s",
-                                    PyEval_GetFuncName(func),
-                                    PyEval_GetFuncDesc(func),
-                                    arg->ob_type->tp_name);
-                        }
-                        Py_DECREF(sum);
-                        goto error;
-                    }
-
-                    if (PySet_GET_SIZE(intersection)) {
-                        Py_ssize_t idx = 0;
-                        PyObject *key;
-                        PyObject *func = PEEK(2 + oparg);
-                        Py_hash_t hash;
-                        _PySet_NextEntry(intersection, &idx, &key, &hash);
-                        if (!PyUnicode_Check(key)) {
-                            PyErr_Format(PyExc_TypeError,
-                                    "%.200s%.200s keywords must be strings",
-                                    PyEval_GetFuncName(func),
-                                    PyEval_GetFuncDesc(func));
-                        } else {
-                            PyErr_Format(PyExc_TypeError,
-                                    "%.200s%.200s got multiple "
-                                    "values for keyword argument '%U'",
-                                    PyEval_GetFuncName(func),
-                                    PyEval_GetFuncDesc(func),
-                                    key);
-                        }
-                        Py_DECREF(intersection);
-                        Py_DECREF(sum);
-                        goto error;
-                    }
-                    Py_DECREF(intersection);
-                }
-
                 if (PyDict_Update(sum, arg) < 0) {
                     if (PyErr_ExceptionMatches(PyExc_AttributeError)) {
                         PyErr_Format(PyExc_TypeError,
-                                "'%.200s' object is not a mapping",
+                                "'%.200s' object is not a mapping1",
                                 arg->ob_type->tp_name);
                     }
                     Py_DECREF(sum);
@@ -2780,6 +2735,61 @@
             DISPATCH();
         }
 
+        TARGET(BUILD_MAP_UNPACK_WITH_CALL) {
+            Py_ssize_t i;
+            PyObject *sum = PyDict_New();
+            if (sum == NULL)
+                goto error;
+
+            for (i = oparg; i > 0; i--) {
+                PyObject *arg = PEEK(i);
+                if (_PyDict_MergeEx(sum, arg, 2) < 0) {
+                    PyObject *func = PEEK(2 + oparg);
+                    if (PyErr_ExceptionMatches(PyExc_AttributeError)) {
+                        PyErr_Format(PyExc_TypeError,
+                                "%.200s%.200s argument after ** "
+                                "must be a mapping, not %.200s",
+                                PyEval_GetFuncName(func),
+                                PyEval_GetFuncDesc(func),
+                                arg->ob_type->tp_name);
+                    }
+                    else if (PyErr_ExceptionMatches(PyExc_KeyError)) {
+                        PyObject *exc, *val, *tb;
+                        PyErr_Fetch(&exc, &val, &tb);
+                        if (val && PyTuple_Check(val) && PyTuple_GET_SIZE(val) == 1) {
+                            PyObject *key = PyTuple_GET_ITEM(val, 0);
+                            if (!PyUnicode_Check(key)) {
+                                PyErr_Format(PyExc_TypeError,
+                                        "%.200s%.200s keywords must be strings",
+                                        PyEval_GetFuncName(func),
+                                        PyEval_GetFuncDesc(func));
+                            } else {
+                                PyErr_Format(PyExc_TypeError,
+                                        "%.200s%.200s got multiple "
+                                        "values for keyword argument '%U'",
+                                        PyEval_GetFuncName(func),
+                                        PyEval_GetFuncDesc(func),
+                                        key);
+                            }
+                            Py_XDECREF(exc);
+                            Py_XDECREF(val);
+                            Py_XDECREF(tb);
+                        }
+                        else {
+                            PyErr_Restore(exc, val, tb);
+                        }
+                    }
+                    Py_DECREF(sum);
+                    goto error;
+                }
+            }
+
+            while (oparg--)
+                Py_DECREF(POP());
+            PUSH(sum);
+            DISPATCH();
+        }
+
         TARGET(MAP_ADD) {
             PyObject *key = TOP();
             PyObject *value = SECOND();

-- 
Repository URL: https://hg.python.org/cpython


More information about the Python-checkins mailing list