From 87dba801c33aa57e9ef934a4f934cb38d6990e16 Mon Sep 17 00:00:00 2001 From: dysu Date: Wed, 27 Jul 2016 01:00:00 -0700 Subject: [PATCH 01/14] initial fix --- Objects/dictobject.c | 82 ++++++++++++++++++++++++++++++++++++++++---- Objects/setobject.c | 14 ++++++++ 2 files changed, 90 insertions(+), 6 deletions(-) diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 40d7d8af6ec2240..1c4d591b49998b0 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -110,6 +110,7 @@ converting the dict to the combined table. */ #define PyDict_MINSIZE 8 + #include "Python.h" #include "internal/pystate.h" #include "dict-common.h" @@ -3919,24 +3920,93 @@ dictviews_sub(PyObject* self, PyObject *other) return result; } +static int +dictitems_contains(_PyDictViewObject *dv, PyObject *obj); + PyObject* _PyDictView_Intersect(PyObject* self, PyObject *other) { - PyObject *result = PySet_New(self); - PyObject *tmp; - _Py_IDENTIFIER(intersection_update); + PyObject *result; + PyObject *it; + PyObject *key; + Py_ssize_t len_self; + int rv; + int (*dict_contains)(_PyDictViewObject *, PyObject *); + + + /* Python interpreter swaps parameters when dict view + is on right side of & */ + if (!PyDictViewSet_Check(self)) { + PyObject *tmp = other; + other = self; + self = tmp; + } + + len_self = dictview_len((_PyDictViewObject *)self); + + + /* if other is a set and self is smaller than other, + reuse set intersection logic */ + if (PyAnySet_Check(other) && len_self <= PyObject_Size(other)) { + _Py_IDENTIFIER(intersection); + + return _PyObject_CallMethodIdObjArgs(other, &PyId_intersection, self, NULL); + } + + /* if other is another dict view, and it is bigger than self, + swap them */ + if (PyDictViewSet_Check(other)) { + Py_ssize_t len_other = dictview_len((_PyDictViewObject *)other); + if (len_other > len_self) { + PyObject *tmp = other; + other = self; + self = tmp; + } + } + /* at this point, self should be bigger than other */ + + result = PySet_New(NULL); if (result == NULL) return NULL; - tmp = _PyObject_CallMethodIdObjArgs(result, &PyId_intersection_update, other, NULL); - if (tmp == NULL) { + it = PyObject_GetIter(other); + if (it == NULL) { Py_DECREF(result); return NULL; } - Py_DECREF(tmp); + if (PyDictKeys_Check(self)) { + dict_contains = dictkeys_contains; + } + /* else PyDictItems_Check(self) */ + else { + dict_contains = dictitems_contains; + } + + while ((key = PyIter_Next(it)) != NULL) { + rv = dict_contains((_PyDictViewObject *)self, key); + if (rv < 0) + goto error; + if (rv) { + if (PySet_Add(result, key)) { + goto error; + } + } + Py_DECREF(key); + } + Py_DECREF(it); + if (PyErr_Occurred()) { + Py_DECREF(result); + return NULL; + } return result; + + error: + Py_DECREF(it); + Py_DECREF(result); + Py_DECREF(key); + return NULL; } static PyObject* diff --git a/Objects/setobject.c b/Objects/setobject.c index 82b58382081625c..540a0ef56d20de0 100644 --- a/Objects/setobject.c +++ b/Objects/setobject.c @@ -31,6 +31,8 @@ the found and not-found case. */ +#include + #include "Python.h" #include "internal/pystate.h" #include "structmember.h" @@ -1269,6 +1271,8 @@ set_intersection(PySetObject *so, PyObject *other) Py_hash_t hash; int rv; + printf("set_intersection\n"); + if ((PyObject *)so == other) return set_copy(so, NULL); @@ -1342,6 +1346,8 @@ set_intersection_multi(PySetObject *so, PyObject *args) Py_ssize_t i; PyObject *result = (PyObject *)so; + printf("set_intersection_multi\n"); + if (PyTuple_GET_SIZE(args) == 0) return set_copy(so, NULL); @@ -1369,6 +1375,8 @@ set_intersection_update(PySetObject *so, PyObject *other) { PyObject *tmp; + printf("set_intersection_update\n"); + tmp = set_intersection(so, other); if (tmp == NULL) return NULL; @@ -1382,6 +1390,8 @@ set_intersection_update_multi(PySetObject *so, PyObject *args) { PyObject *tmp; + printf("set_intersection_update_multi\n"); + tmp = set_intersection_multi(so, args); if (tmp == NULL) return NULL; @@ -1396,6 +1406,8 @@ PyDoc_STRVAR(intersection_update_doc, static PyObject * set_and(PySetObject *so, PyObject *other) { + printf("set_and\n"); + if (!PyAnySet_Check(so) || !PyAnySet_Check(other)) Py_RETURN_NOTIMPLEMENTED; return set_intersection(so, other); @@ -1406,6 +1418,8 @@ set_iand(PySetObject *so, PyObject *other) { PyObject *result; + printf("set_iand\n"); + if (!PyAnySet_Check(other)) Py_RETURN_NOTIMPLEMENTED; result = set_intersection_update(so, other); From d3c660305dd8d5f280308b6016d4e38644b3a091 Mon Sep 17 00:00:00 2001 From: dysu Date: Wed, 27 Jul 2016 22:08:27 -0700 Subject: [PATCH 02/14] removed printf calls --- Objects/setobject.c | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/Objects/setobject.c b/Objects/setobject.c index 540a0ef56d20de0..82b58382081625c 100644 --- a/Objects/setobject.c +++ b/Objects/setobject.c @@ -31,8 +31,6 @@ the found and not-found case. */ -#include - #include "Python.h" #include "internal/pystate.h" #include "structmember.h" @@ -1271,8 +1269,6 @@ set_intersection(PySetObject *so, PyObject *other) Py_hash_t hash; int rv; - printf("set_intersection\n"); - if ((PyObject *)so == other) return set_copy(so, NULL); @@ -1346,8 +1342,6 @@ set_intersection_multi(PySetObject *so, PyObject *args) Py_ssize_t i; PyObject *result = (PyObject *)so; - printf("set_intersection_multi\n"); - if (PyTuple_GET_SIZE(args) == 0) return set_copy(so, NULL); @@ -1375,8 +1369,6 @@ set_intersection_update(PySetObject *so, PyObject *other) { PyObject *tmp; - printf("set_intersection_update\n"); - tmp = set_intersection(so, other); if (tmp == NULL) return NULL; @@ -1390,8 +1382,6 @@ set_intersection_update_multi(PySetObject *so, PyObject *args) { PyObject *tmp; - printf("set_intersection_update_multi\n"); - tmp = set_intersection_multi(so, args); if (tmp == NULL) return NULL; @@ -1406,8 +1396,6 @@ PyDoc_STRVAR(intersection_update_doc, static PyObject * set_and(PySetObject *so, PyObject *other) { - printf("set_and\n"); - if (!PyAnySet_Check(so) || !PyAnySet_Check(other)) Py_RETURN_NOTIMPLEMENTED; return set_intersection(so, other); @@ -1418,8 +1406,6 @@ set_iand(PySetObject *so, PyObject *other) { PyObject *result; - printf("set_iand\n"); - if (!PyAnySet_Check(other)) Py_RETURN_NOTIMPLEMENTED; result = set_intersection_update(so, other); From 8b264eb57db90ec04fa6995d66dd98ee4b6b3558 Mon Sep 17 00:00:00 2001 From: dysu Date: Thu, 28 Jul 2016 00:29:02 -0700 Subject: [PATCH 03/14] added unit tests; need to add performance tests --- test_dictview_intersection.py | 199 ++++++++++++++++++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 test_dictview_intersection.py diff --git a/test_dictview_intersection.py b/test_dictview_intersection.py new file mode 100644 index 000000000000000..78f6b6ff506f787 --- /dev/null +++ b/test_dictview_intersection.py @@ -0,0 +1,199 @@ +from __future__ import print_function +import unittest +from collections import namedtuple + +Pair = namedtuple('Pair', ['big', 'small']) + + +def combos(op1, op2): + return ( + (op1.big, op2.small), + (op2.small, op1.big), + (op2.big, op1.small), + (op1.small, op2.big) + ) + + +class Generator: + def __init__(self, big, small): + self._big = big + self._small = small + + @property + def big(self): + return (i for i in self._big) + + @property + def small(self): + return (i for i in self._small) + + +# noinspection PyTypeChecker +class TestDictViewIntersection(unittest.TestCase): + + def intersectionAssertRaises(self, op1, op2, exception): + for left, right in combos(op1, op2): + with self.assertRaises(exception): + val = left & right + + def intersectionAssertEqual(self, op1, op2, expected): + for left, right in combos(op1, op2): + self.assertEqual(left & right, expected) + + + + def setUp(self): + print("\nIn method", self._testMethodName) + + self.dict = Pair( + big={1: 1, 2: 2, 3: 3}, + small={1: 1} + ) + + self.dict_keys = Pair( + big=self.dict.big.keys(), + small=self.dict.small.keys() + ) + + self.dict_values = Pair( + big=self.dict.big.values(), + small=self.dict.small.values() + ) + + self.dict_items = Pair( + big=self.dict.big.items(), + small=self.dict.small.items() + ) + + self.set = Pair( + big={1, 2, 3}, + small={1} + ) + + self.list = Pair( + big=[1, 2, 3], + small=[1] + ) + + self.generator = Generator(self.list.big, self.list.small) + + + def test001_dict_dict(self): + self.intersectionAssertRaises(self.dict, self.dict, TypeError) + + def test002_dict_dict_keys(self): + self.intersectionAssertEqual(self.dict, self.dict_keys, {1}) + + + def test003_dict_dict_values(self): + self.intersectionAssertRaises(self.dict, self.dict_values, TypeError) + + def test004_dict_dict_items(self): + self.intersectionAssertEqual(Pair( + big={(1, 1): 1, (2, 2): 2, (3, 3): 3}, + small={(1, 1): 1} + ), self.dict_items, {(1, 1)}) + + def test005_dict_set(self): + self.intersectionAssertRaises(self.dict, self.set, TypeError) + + def test006_dict_list(self): + self.intersectionAssertRaises(self.dict, self.list, TypeError) + + def test007_dict_generator(self): + self.intersectionAssertRaises(self.dict, self.generator, TypeError) + + def test008_dict_keys_dict_keys(self): + self.intersectionAssertEqual(self.dict_keys, self.dict_keys, {1}) + + def test009_dict_keys_dict_values(self): + self.intersectionAssertEqual(self.dict_keys, self.dict_values, {1}) + + def test010_dict_keys_dict_items(self): + self.intersectionAssertEqual(Pair( + big={(1, 1): 1, (2, 2): 2, (3, 3): 3}.keys(), + small={(1, 1): 1}.keys() + ), self.dict_items, {(1, 1)}) + + def test011_dict_keys_set(self): + self.intersectionAssertEqual(self.dict_keys, self.set, {1}) + + def test012_dict_keys_list(self): + self.intersectionAssertEqual(self.dict_keys, self.list, {1}) + + def test013_dict_keys_generator(self): + self.intersectionAssertEqual(self.dict_keys, self.generator, {1}) + + def test014_dict_values_dict_values(self): + self.intersectionAssertRaises(self.dict_values, self.dict_values, TypeError) + + def test015_dict_values_dict_items(self): + self.intersectionAssertEqual(Pair( + big={1: (1, 1), 2: (2, 2), 3: (3, 3)}.values(), + small={1: (1, 1)}.values() + ), self.dict_items, {(1, 1)}) + + def test016_dict_values_set(self): + self.intersectionAssertRaises(self.dict_values, self.set, TypeError) + + def test017_dict_values_list(self): + self.intersectionAssertRaises(self.dict_values, self.list, TypeError) + + def test018_dict_values_generator(self): + self.intersectionAssertRaises(self.dict_values, self.generator, TypeError) + + def test019_dict_items_dict_items(self): + self.intersectionAssertEqual(self.dict_items, self.dict_items, {(1, 1)}) + + def test020_dict_items_set(self): + self.intersectionAssertEqual( + self.dict_items, + Pair( + big={(1, 1), (2, 2), (3, 3)}, + small={(1, 1)} + ), + {(1, 1)} + ) + + def test021_dict_items_list(self): + self.intersectionAssertEqual( + self.dict_items, + Pair( + big=[(1, 1), (2, 2), (3, 3)], + small=[(1, 1)] + ), + {(1, 1)} + ) + + def test022_dict_items_generator(self): + self.intersectionAssertEqual( + self.dict_items, + Generator( + big=[(1, 1), (2, 2), (3, 3)], + small=[(1, 1)] + ), + {(1, 1)} + ) + + def test023_set_set(self): + self.intersectionAssertEqual(self.set, self.set, {1}) + + def test024_set_list(self): + self.intersectionAssertRaises(self.set, self.list, TypeError) + + def test025_set_generator(self): + self.intersectionAssertRaises(self.set, self.generator, TypeError) + + def test026_list_list(self): + self.intersectionAssertRaises(self.list, self.list, TypeError) + + def test027_list_generator(self): + self.intersectionAssertRaises(self.list, self.generator, TypeError) + + def test_028_generator_generator(self): + self.intersectionAssertRaises(self.generator, self.generator, TypeError) + + +if __name__ == '__main__': + unittest.main() + From 1efe3fb0c017e64f2b04ba18c1ace96b2f88ab18 Mon Sep 17 00:00:00 2001 From: dysu Date: Sun, 31 Jul 2016 23:07:04 -0700 Subject: [PATCH 04/14] added performance tests --- Objects/dictobject.c | 1 - speed_test.sh | 117 ++++++++++++++++++++++++++++++++++ test_dictview_intersection.py | 32 +++++++++- 3 files changed, 146 insertions(+), 4 deletions(-) create mode 100755 speed_test.sh diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 1c4d591b49998b0..e01dcfda32c9b0b 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -3965,7 +3965,6 @@ _PyDictView_Intersect(PyObject* self, PyObject *other) } /* at this point, self should be bigger than other */ - result = PySet_New(NULL); if (result == NULL) return NULL; diff --git a/speed_test.sh b/speed_test.sh new file mode 100755 index 000000000000000..9464e36697b1b73 --- /dev/null +++ b/speed_test.sh @@ -0,0 +1,117 @@ +#!/usr/bin/env bash + +echo "dictkeys & dictkeys" +./python \ + -m timeit \ + -s "dk1 = dict.fromkeys(range(10000000), 0).keys(); dk2 = {0: 0}.keys()" \ + "dk1 & dk2" + +./python \ + -m timeit \ + -s "dk1 = dict.fromkeys(range(10000000), 0).keys(); dk2 = {0: 0}.keys()" \ + "dk2 & dk1" + + +echo "dictkeys & dictitems" +./python \ + -m timeit \ + -s "dk = {(i, i): 0 for i in range(10000000)}.keys(); di = {0: 0}.items()" \ + "dk & di" + +./python \ + -m timeit \ + -s "dk = {(i, i): 0 for i in range(10000000)}.keys(); di = {0: 0}.items()" \ + "di & dk" + +./python \ + -m timeit \ + -s "di = dict.fromkeys(range(10000000), 0).items(); dk = {(0, 0): 0}.keys()" \ + "dk & di" + +./python \ + -m timeit \ + -s "di = dict.fromkeys(range(10000000), 0).items(); dk = {(0, 0): 0}.keys()" \ + "di & dk" + + + +echo "dictkeys & set" +./python \ + -m timeit \ + -s "dk = dict.fromkeys(range(10000000), 0).keys(); s = {0}" \ + "dk & s" + +./python \ + -m timeit \ + -s "dk = dict.fromkeys(range(10000000), 0).keys(); s = {0}" \ + "s & dk" + +./python \ + -m timeit \ + -s "s = set(range(10000000)); dk = {0: 0}.keys()" \ + "dk & s" + +./python \ + -m timeit \ + -s "s = set(range(10000000)); dk = {0: 0}.keys()" \ + "s & dk" + + + + +echo "dictitems & dictitems" +./python \ + -m timeit \ + -s "di1 = dict.fromkeys(range(10000000), 0).items(); di2 = {0: 0}.items()" \ + "di1 & di2" + +./python \ + -m timeit \ + -s "di1 = dict.fromkeys(range(10000000), 0).items(); di2 = {0: 0}.items()" \ + "di2 & di1" + + +echo "dictitems & set" +./python \ + -m timeit \ + -s "di = dict.fromkeys(range(10000000), 0).items(); s = {(0, 0)}" \ + "di & s" + +./python \ + -m timeit \ + -s "di = dict.fromkeys(range(10000000), 0).items(); s = {(0, 0)}" \ + "s & di" + +./python \ + -m timeit \ + -s "s = {(i, i) for i in range(10000000)}; di = {0: 0}.items()" \ + "s & di" + +./python \ + -m timeit \ + -s "s = {(i, i) for i in range(10000000)}; di = {0: 0}.items()" \ + "di & s" + + +# RESULTS: +# dictkeys & dictkeys +# 1000000 loops, best of 3: 0.466 usec per loop +# 1000000 loops, best of 3: 0.466 usec per loop +# dictkeys & dictitems +# 1000000 loops, best of 3: 0.581 usec per loop +# 1000000 loops, best of 3: 0.775 usec per loop +# 1000000 loops, best of 3: 0.665 usec per loop +# 1000000 loops, best of 3: 0.688 usec per loop +# dictkeys & set +# 1000000 loops, best of 3: 0.609 usec per loop +# 1000000 loops, best of 3: 0.643 usec per loop +# 1000000 loops, best of 3: 0.89 usec per loop +# 1000000 loops, best of 3: 0.685 usec per loop +# dictitems & dictitems +# 1000000 loops, best of 3: 0.543 usec per loop +# 1000000 loops, best of 3: 0.544 usec per loop +# dictitems & set +# 1000000 loops, best of 3: 0.659 usec per loop +# 1000000 loops, best of 3: 0.684 usec per loop +# 1000000 loops, best of 3: 0.832 usec per loop +# 1000000 loops, best of 3: 0.796 usec per loop diff --git a/test_dictview_intersection.py b/test_dictview_intersection.py index 78f6b6ff506f787..b0721ce42c09a46 100644 --- a/test_dictview_intersection.py +++ b/test_dictview_intersection.py @@ -1,4 +1,3 @@ -from __future__ import print_function import unittest from collections import namedtuple @@ -79,73 +78,92 @@ def setUp(self): def test001_dict_dict(self): + """Should raise exception""" self.intersectionAssertRaises(self.dict, self.dict, TypeError) def test002_dict_dict_keys(self): + """Should be slow. Instead should use dict.keys() & dict.keys()""" self.intersectionAssertEqual(self.dict, self.dict_keys, {1}) - def test003_dict_dict_values(self): + """Should raise exception""" self.intersectionAssertRaises(self.dict, self.dict_values, TypeError) def test004_dict_dict_items(self): + """Should be slow. Instead should use dict.keys() & dict.items()""" self.intersectionAssertEqual(Pair( big={(1, 1): 1, (2, 2): 2, (3, 3): 3}, small={(1, 1): 1} ), self.dict_items, {(1, 1)}) def test005_dict_set(self): + """Should raise exception""" self.intersectionAssertRaises(self.dict, self.set, TypeError) def test006_dict_list(self): + """Should raise exception""" self.intersectionAssertRaises(self.dict, self.list, TypeError) def test007_dict_generator(self): + """Should raise exception""" self.intersectionAssertRaises(self.dict, self.generator, TypeError) def test008_dict_keys_dict_keys(self): + """Should be fast""" self.intersectionAssertEqual(self.dict_keys, self.dict_keys, {1}) def test009_dict_keys_dict_values(self): + """Should be slow""" self.intersectionAssertEqual(self.dict_keys, self.dict_values, {1}) def test010_dict_keys_dict_items(self): + """Should be fast""" self.intersectionAssertEqual(Pair( big={(1, 1): 1, (2, 2): 2, (3, 3): 3}.keys(), small={(1, 1): 1}.keys() ), self.dict_items, {(1, 1)}) def test011_dict_keys_set(self): + """Should be fast""" self.intersectionAssertEqual(self.dict_keys, self.set, {1}) def test012_dict_keys_list(self): + """Should be slow""" self.intersectionAssertEqual(self.dict_keys, self.list, {1}) def test013_dict_keys_generator(self): + """Should be slow""" self.intersectionAssertEqual(self.dict_keys, self.generator, {1}) def test014_dict_values_dict_values(self): + """Should raise exception""" self.intersectionAssertRaises(self.dict_values, self.dict_values, TypeError) def test015_dict_values_dict_items(self): + """Should be slow""" self.intersectionAssertEqual(Pair( big={1: (1, 1), 2: (2, 2), 3: (3, 3)}.values(), small={1: (1, 1)}.values() ), self.dict_items, {(1, 1)}) def test016_dict_values_set(self): + """Should raise exception""" self.intersectionAssertRaises(self.dict_values, self.set, TypeError) def test017_dict_values_list(self): + """Should be raise exception""" self.intersectionAssertRaises(self.dict_values, self.list, TypeError) def test018_dict_values_generator(self): + """Should raise exception""" self.intersectionAssertRaises(self.dict_values, self.generator, TypeError) def test019_dict_items_dict_items(self): + """Should be fast""" self.intersectionAssertEqual(self.dict_items, self.dict_items, {(1, 1)}) def test020_dict_items_set(self): + """Should be fast""" self.intersectionAssertEqual( self.dict_items, Pair( @@ -156,6 +174,7 @@ def test020_dict_items_set(self): ) def test021_dict_items_list(self): + """Should be slow""" self.intersectionAssertEqual( self.dict_items, Pair( @@ -166,6 +185,7 @@ def test021_dict_items_list(self): ) def test022_dict_items_generator(self): + """Should be slow""" self.intersectionAssertEqual( self.dict_items, Generator( @@ -176,21 +196,27 @@ def test022_dict_items_generator(self): ) def test023_set_set(self): + """Should be fast. Outside of scope of fix.""" self.intersectionAssertEqual(self.set, self.set, {1}) def test024_set_list(self): + """Should raise exception. Outside of scope of fix.""" self.intersectionAssertRaises(self.set, self.list, TypeError) def test025_set_generator(self): + """Should raise exception. Outside of scope of fix.""" self.intersectionAssertRaises(self.set, self.generator, TypeError) def test026_list_list(self): + """Should raise exception. Outside of scope of fix.""" self.intersectionAssertRaises(self.list, self.list, TypeError) def test027_list_generator(self): + """Should raise exception. Outside of scope of fix.""" self.intersectionAssertRaises(self.list, self.generator, TypeError) - def test_028_generator_generator(self): + def test028_generator_generator(self): + """Should raise exception. Outside of scope of fix.""" self.intersectionAssertRaises(self.generator, self.generator, TypeError) From 09336be6e45bcc36f907b58d122705fab2552704 Mon Sep 17 00:00:00 2001 From: dysu Date: Sun, 31 Jul 2016 23:16:55 -0700 Subject: [PATCH 05/14] Create patch file from this --- Objects/dictobject.c | 1 - speed_test.sh | 117 ------------------ test_dictview_intersection.py | 225 ---------------------------------- 3 files changed, 343 deletions(-) delete mode 100755 speed_test.sh delete mode 100644 test_dictview_intersection.py diff --git a/Objects/dictobject.c b/Objects/dictobject.c index e01dcfda32c9b0b..885174710ab72d1 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -110,7 +110,6 @@ converting the dict to the combined table. */ #define PyDict_MINSIZE 8 - #include "Python.h" #include "internal/pystate.h" #include "dict-common.h" diff --git a/speed_test.sh b/speed_test.sh deleted file mode 100755 index 9464e36697b1b73..000000000000000 --- a/speed_test.sh +++ /dev/null @@ -1,117 +0,0 @@ -#!/usr/bin/env bash - -echo "dictkeys & dictkeys" -./python \ - -m timeit \ - -s "dk1 = dict.fromkeys(range(10000000), 0).keys(); dk2 = {0: 0}.keys()" \ - "dk1 & dk2" - -./python \ - -m timeit \ - -s "dk1 = dict.fromkeys(range(10000000), 0).keys(); dk2 = {0: 0}.keys()" \ - "dk2 & dk1" - - -echo "dictkeys & dictitems" -./python \ - -m timeit \ - -s "dk = {(i, i): 0 for i in range(10000000)}.keys(); di = {0: 0}.items()" \ - "dk & di" - -./python \ - -m timeit \ - -s "dk = {(i, i): 0 for i in range(10000000)}.keys(); di = {0: 0}.items()" \ - "di & dk" - -./python \ - -m timeit \ - -s "di = dict.fromkeys(range(10000000), 0).items(); dk = {(0, 0): 0}.keys()" \ - "dk & di" - -./python \ - -m timeit \ - -s "di = dict.fromkeys(range(10000000), 0).items(); dk = {(0, 0): 0}.keys()" \ - "di & dk" - - - -echo "dictkeys & set" -./python \ - -m timeit \ - -s "dk = dict.fromkeys(range(10000000), 0).keys(); s = {0}" \ - "dk & s" - -./python \ - -m timeit \ - -s "dk = dict.fromkeys(range(10000000), 0).keys(); s = {0}" \ - "s & dk" - -./python \ - -m timeit \ - -s "s = set(range(10000000)); dk = {0: 0}.keys()" \ - "dk & s" - -./python \ - -m timeit \ - -s "s = set(range(10000000)); dk = {0: 0}.keys()" \ - "s & dk" - - - - -echo "dictitems & dictitems" -./python \ - -m timeit \ - -s "di1 = dict.fromkeys(range(10000000), 0).items(); di2 = {0: 0}.items()" \ - "di1 & di2" - -./python \ - -m timeit \ - -s "di1 = dict.fromkeys(range(10000000), 0).items(); di2 = {0: 0}.items()" \ - "di2 & di1" - - -echo "dictitems & set" -./python \ - -m timeit \ - -s "di = dict.fromkeys(range(10000000), 0).items(); s = {(0, 0)}" \ - "di & s" - -./python \ - -m timeit \ - -s "di = dict.fromkeys(range(10000000), 0).items(); s = {(0, 0)}" \ - "s & di" - -./python \ - -m timeit \ - -s "s = {(i, i) for i in range(10000000)}; di = {0: 0}.items()" \ - "s & di" - -./python \ - -m timeit \ - -s "s = {(i, i) for i in range(10000000)}; di = {0: 0}.items()" \ - "di & s" - - -# RESULTS: -# dictkeys & dictkeys -# 1000000 loops, best of 3: 0.466 usec per loop -# 1000000 loops, best of 3: 0.466 usec per loop -# dictkeys & dictitems -# 1000000 loops, best of 3: 0.581 usec per loop -# 1000000 loops, best of 3: 0.775 usec per loop -# 1000000 loops, best of 3: 0.665 usec per loop -# 1000000 loops, best of 3: 0.688 usec per loop -# dictkeys & set -# 1000000 loops, best of 3: 0.609 usec per loop -# 1000000 loops, best of 3: 0.643 usec per loop -# 1000000 loops, best of 3: 0.89 usec per loop -# 1000000 loops, best of 3: 0.685 usec per loop -# dictitems & dictitems -# 1000000 loops, best of 3: 0.543 usec per loop -# 1000000 loops, best of 3: 0.544 usec per loop -# dictitems & set -# 1000000 loops, best of 3: 0.659 usec per loop -# 1000000 loops, best of 3: 0.684 usec per loop -# 1000000 loops, best of 3: 0.832 usec per loop -# 1000000 loops, best of 3: 0.796 usec per loop diff --git a/test_dictview_intersection.py b/test_dictview_intersection.py deleted file mode 100644 index b0721ce42c09a46..000000000000000 --- a/test_dictview_intersection.py +++ /dev/null @@ -1,225 +0,0 @@ -import unittest -from collections import namedtuple - -Pair = namedtuple('Pair', ['big', 'small']) - - -def combos(op1, op2): - return ( - (op1.big, op2.small), - (op2.small, op1.big), - (op2.big, op1.small), - (op1.small, op2.big) - ) - - -class Generator: - def __init__(self, big, small): - self._big = big - self._small = small - - @property - def big(self): - return (i for i in self._big) - - @property - def small(self): - return (i for i in self._small) - - -# noinspection PyTypeChecker -class TestDictViewIntersection(unittest.TestCase): - - def intersectionAssertRaises(self, op1, op2, exception): - for left, right in combos(op1, op2): - with self.assertRaises(exception): - val = left & right - - def intersectionAssertEqual(self, op1, op2, expected): - for left, right in combos(op1, op2): - self.assertEqual(left & right, expected) - - - - def setUp(self): - print("\nIn method", self._testMethodName) - - self.dict = Pair( - big={1: 1, 2: 2, 3: 3}, - small={1: 1} - ) - - self.dict_keys = Pair( - big=self.dict.big.keys(), - small=self.dict.small.keys() - ) - - self.dict_values = Pair( - big=self.dict.big.values(), - small=self.dict.small.values() - ) - - self.dict_items = Pair( - big=self.dict.big.items(), - small=self.dict.small.items() - ) - - self.set = Pair( - big={1, 2, 3}, - small={1} - ) - - self.list = Pair( - big=[1, 2, 3], - small=[1] - ) - - self.generator = Generator(self.list.big, self.list.small) - - - def test001_dict_dict(self): - """Should raise exception""" - self.intersectionAssertRaises(self.dict, self.dict, TypeError) - - def test002_dict_dict_keys(self): - """Should be slow. Instead should use dict.keys() & dict.keys()""" - self.intersectionAssertEqual(self.dict, self.dict_keys, {1}) - - def test003_dict_dict_values(self): - """Should raise exception""" - self.intersectionAssertRaises(self.dict, self.dict_values, TypeError) - - def test004_dict_dict_items(self): - """Should be slow. Instead should use dict.keys() & dict.items()""" - self.intersectionAssertEqual(Pair( - big={(1, 1): 1, (2, 2): 2, (3, 3): 3}, - small={(1, 1): 1} - ), self.dict_items, {(1, 1)}) - - def test005_dict_set(self): - """Should raise exception""" - self.intersectionAssertRaises(self.dict, self.set, TypeError) - - def test006_dict_list(self): - """Should raise exception""" - self.intersectionAssertRaises(self.dict, self.list, TypeError) - - def test007_dict_generator(self): - """Should raise exception""" - self.intersectionAssertRaises(self.dict, self.generator, TypeError) - - def test008_dict_keys_dict_keys(self): - """Should be fast""" - self.intersectionAssertEqual(self.dict_keys, self.dict_keys, {1}) - - def test009_dict_keys_dict_values(self): - """Should be slow""" - self.intersectionAssertEqual(self.dict_keys, self.dict_values, {1}) - - def test010_dict_keys_dict_items(self): - """Should be fast""" - self.intersectionAssertEqual(Pair( - big={(1, 1): 1, (2, 2): 2, (3, 3): 3}.keys(), - small={(1, 1): 1}.keys() - ), self.dict_items, {(1, 1)}) - - def test011_dict_keys_set(self): - """Should be fast""" - self.intersectionAssertEqual(self.dict_keys, self.set, {1}) - - def test012_dict_keys_list(self): - """Should be slow""" - self.intersectionAssertEqual(self.dict_keys, self.list, {1}) - - def test013_dict_keys_generator(self): - """Should be slow""" - self.intersectionAssertEqual(self.dict_keys, self.generator, {1}) - - def test014_dict_values_dict_values(self): - """Should raise exception""" - self.intersectionAssertRaises(self.dict_values, self.dict_values, TypeError) - - def test015_dict_values_dict_items(self): - """Should be slow""" - self.intersectionAssertEqual(Pair( - big={1: (1, 1), 2: (2, 2), 3: (3, 3)}.values(), - small={1: (1, 1)}.values() - ), self.dict_items, {(1, 1)}) - - def test016_dict_values_set(self): - """Should raise exception""" - self.intersectionAssertRaises(self.dict_values, self.set, TypeError) - - def test017_dict_values_list(self): - """Should be raise exception""" - self.intersectionAssertRaises(self.dict_values, self.list, TypeError) - - def test018_dict_values_generator(self): - """Should raise exception""" - self.intersectionAssertRaises(self.dict_values, self.generator, TypeError) - - def test019_dict_items_dict_items(self): - """Should be fast""" - self.intersectionAssertEqual(self.dict_items, self.dict_items, {(1, 1)}) - - def test020_dict_items_set(self): - """Should be fast""" - self.intersectionAssertEqual( - self.dict_items, - Pair( - big={(1, 1), (2, 2), (3, 3)}, - small={(1, 1)} - ), - {(1, 1)} - ) - - def test021_dict_items_list(self): - """Should be slow""" - self.intersectionAssertEqual( - self.dict_items, - Pair( - big=[(1, 1), (2, 2), (3, 3)], - small=[(1, 1)] - ), - {(1, 1)} - ) - - def test022_dict_items_generator(self): - """Should be slow""" - self.intersectionAssertEqual( - self.dict_items, - Generator( - big=[(1, 1), (2, 2), (3, 3)], - small=[(1, 1)] - ), - {(1, 1)} - ) - - def test023_set_set(self): - """Should be fast. Outside of scope of fix.""" - self.intersectionAssertEqual(self.set, self.set, {1}) - - def test024_set_list(self): - """Should raise exception. Outside of scope of fix.""" - self.intersectionAssertRaises(self.set, self.list, TypeError) - - def test025_set_generator(self): - """Should raise exception. Outside of scope of fix.""" - self.intersectionAssertRaises(self.set, self.generator, TypeError) - - def test026_list_list(self): - """Should raise exception. Outside of scope of fix.""" - self.intersectionAssertRaises(self.list, self.list, TypeError) - - def test027_list_generator(self): - """Should raise exception. Outside of scope of fix.""" - self.intersectionAssertRaises(self.list, self.generator, TypeError) - - def test028_generator_generator(self): - """Should raise exception. Outside of scope of fix.""" - self.intersectionAssertRaises(self.generator, self.generator, TypeError) - - -if __name__ == '__main__': - unittest.main() - From 9ce42683219a418f4a571a5d92369bf966c82ea8 Mon Sep 17 00:00:00 2001 From: Forest Gregg Date: Thu, 14 Jun 2018 13:56:11 -0500 Subject: [PATCH 06/14] add news entry --- .../Core and Builtins/2018-06-14-13-55-45.bpo-27575.mMYgzv.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2018-06-14-13-55-45.bpo-27575.mMYgzv.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2018-06-14-13-55-45.bpo-27575.mMYgzv.rst b/Misc/NEWS.d/next/Core and Builtins/2018-06-14-13-55-45.bpo-27575.mMYgzv.rst new file mode 100644 index 000000000000000..054276d666f89bc --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2018-06-14-13-55-45.bpo-27575.mMYgzv.rst @@ -0,0 +1,2 @@ +Improve speed of dictview intersection by directly using set intersection +logic. Patch by David Hsu. From f290942c8fb52abf0b91edc01e064933219bdad7 Mon Sep 17 00:00:00 2001 From: Forest Gregg Date: Thu, 14 Jun 2018 17:08:01 -0500 Subject: [PATCH 07/14] test that intersection of dictview with frozenset returns set --- Lib/test/test_dictviews.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_dictviews.py b/Lib/test/test_dictviews.py index 2763cbfb4bbc64f..03edd7f7a7a86d2 100644 --- a/Lib/test/test_dictviews.py +++ b/Lib/test/test_dictviews.py @@ -99,6 +99,7 @@ def test_keys_set_operations(self): self.assertEqual(d1.keys() & set(d2.keys()), {'b'}) self.assertEqual(d1.keys() & set(d3.keys()), set()) self.assertEqual(d1.keys() & tuple(d1.keys()), {'a', 'b'}) + self.assertEqual(d1.keys() & frozenset(d1.keys()), {'a', 'b'}) self.assertEqual(d1.keys() | d1.keys(), {'a', 'b'}) self.assertEqual(d1.keys() | d2.keys(), {'a', 'b', 'c'}) From 196f7e409f2a7856fec443c642f396e667cf8bfc Mon Sep 17 00:00:00 2001 From: Forest Gregg Date: Thu, 14 Jun 2018 21:06:37 -0500 Subject: [PATCH 08/14] test all code paths in _PyDictView_Intersect --- Lib/test/test_dictviews.py | 7 ++++++- Objects/dictobject.c | 9 ++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_dictviews.py b/Lib/test/test_dictviews.py index 03edd7f7a7a86d2..090726dfe53cb2a 100644 --- a/Lib/test/test_dictviews.py +++ b/Lib/test/test_dictviews.py @@ -92,6 +92,8 @@ def test_keys_set_operations(self): d1 = {'a': 1, 'b': 2} d2 = {'b': 3, 'c': 2} d3 = {'d': 4, 'e': 5} + d4 = {'d': 4} + self.assertEqual(d1.keys() & d1.keys(), {'a', 'b'}) self.assertEqual(d1.keys() & d2.keys(), {'b'}) self.assertEqual(d1.keys() & d3.keys(), set()) @@ -99,7 +101,10 @@ def test_keys_set_operations(self): self.assertEqual(d1.keys() & set(d2.keys()), {'b'}) self.assertEqual(d1.keys() & set(d3.keys()), set()) self.assertEqual(d1.keys() & tuple(d1.keys()), {'a', 'b'}) - self.assertEqual(d1.keys() & frozenset(d1.keys()), {'a', 'b'}) + self.assertEqual(d3.keys() & d4.keys(), {'d'}) + self.assertEqual(d4.keys() & d3.keys(), {'d'}) + self.assertIsInstance(d4.keys() & frozenset(d3.keys()), set) + self.assertIsInstance(frozenset(d3.keys()) & d4.keys(), frozenset) self.assertEqual(d1.keys() | d1.keys(), {'a', 'b'}) self.assertEqual(d1.keys() | d2.keys(), {'a', 'b', 'c'}) diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 885174710ab72d1..8c2186a7301fab4 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -3932,13 +3932,12 @@ _PyDictView_Intersect(PyObject* self, PyObject *other) int rv; int (*dict_contains)(_PyDictViewObject *, PyObject *); - /* Python interpreter swaps parameters when dict view is on right side of & */ if (!PyDictViewSet_Check(self)) { - PyObject *tmp = other; - other = self; - self = tmp; + _Py_IDENTIFIER(intersection); + + return _PyObject_CallMethodIdObjArgs(self, &PyId_intersection, other, NULL); } len_self = dictview_len((_PyDictViewObject *)self); @@ -3946,7 +3945,7 @@ _PyDictView_Intersect(PyObject* self, PyObject *other) /* if other is a set and self is smaller than other, reuse set intersection logic */ - if (PyAnySet_Check(other) && len_self <= PyObject_Size(other)) { + if (PySet_Check(other) && len_self <= PyObject_Size(other)) { _Py_IDENTIFIER(intersection); return _PyObject_CallMethodIdObjArgs(other, &PyId_intersection, self, NULL); From d5e45b3e7f37e7eef18c0584aac42ff1117b5dfc Mon Sep 17 00:00:00 2001 From: Forest Gregg Date: Fri, 15 Jun 2018 11:14:28 -0500 Subject: [PATCH 09/14] tighten up test for dictview intersection --- Lib/test/test_dictviews.py | 6 ++++++ Objects/dictobject.c | 7 +++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_dictviews.py b/Lib/test/test_dictviews.py index 090726dfe53cb2a..7b16971facc6ec7 100644 --- a/Lib/test/test_dictviews.py +++ b/Lib/test/test_dictviews.py @@ -94,6 +94,10 @@ def test_keys_set_operations(self): d3 = {'d': 4, 'e': 5} d4 = {'d': 4} + class CustomSet(set): + def intersection(self, other): + return CustomSet(super().intersection(other)) + self.assertEqual(d1.keys() & d1.keys(), {'a', 'b'}) self.assertEqual(d1.keys() & d2.keys(), {'b'}) self.assertEqual(d1.keys() & d3.keys(), set()) @@ -103,8 +107,10 @@ def test_keys_set_operations(self): self.assertEqual(d1.keys() & tuple(d1.keys()), {'a', 'b'}) self.assertEqual(d3.keys() & d4.keys(), {'d'}) self.assertEqual(d4.keys() & d3.keys(), {'d'}) + self.assertEqual(d4.keys() & set(d3.keys()), {'d'}) self.assertIsInstance(d4.keys() & frozenset(d3.keys()), set) self.assertIsInstance(frozenset(d3.keys()) & d4.keys(), frozenset) + self.assertIs(type(d4.keys() & CustomSet(d3.keys())), set) self.assertEqual(d1.keys() | d1.keys(), {'a', 'b'}) self.assertEqual(d1.keys() | d2.keys(), {'a', 'b', 'c'}) diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 8c2186a7301fab4..61eeffa4f463fbd 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -3945,7 +3945,7 @@ _PyDictView_Intersect(PyObject* self, PyObject *other) /* if other is a set and self is smaller than other, reuse set intersection logic */ - if (PySet_Check(other) && len_self <= PyObject_Size(other)) { + if (Py_TYPE(other) == &PySet_Type && len_self <= PyObject_Size(other)) { _Py_IDENTIFIER(intersection); return _PyObject_CallMethodIdObjArgs(other, &PyId_intersection, self, NULL); @@ -3962,7 +3962,10 @@ _PyDictView_Intersect(PyObject* self, PyObject *other) } } - /* at this point, self should be bigger than other */ + /* at this point, one of two things should be true + 1. self and other are both dictviews and self is bigger + than other + 2. self is a dictview and other is not a dictview */ result = PySet_New(NULL); if (result == NULL) return NULL; From 285495fce3ec68395231690945ea09fe460b983f Mon Sep 17 00:00:00 2001 From: Forest Gregg Date: Fri, 15 Jun 2018 11:19:15 -0500 Subject: [PATCH 10/14] clearer comment in dictview intersection --- Objects/dictobject.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 61eeffa4f463fbd..f2e8b69654e0c1d 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -3962,10 +3962,9 @@ _PyDictView_Intersect(PyObject* self, PyObject *other) } } - /* at this point, one of two things should be true - 1. self and other are both dictviews and self is bigger - than other - 2. self is a dictview and other is not a dictview */ + /* at this point, two things should be true + 1. self is a dictview + 2. if other is a dictview then it is smaller than self */ result = PySet_New(NULL); if (result == NULL) return NULL; From d309239545a2fe0ade4cb93303285155837d4c3f Mon Sep 17 00:00:00 2001 From: Forest Gregg Date: Sat, 16 Jun 2018 08:28:34 -0500 Subject: [PATCH 11/14] restore intended behavior explained in https://bugs.python.org/issue33874 --- Lib/test/test_dictviews.py | 4 +++- Objects/dictobject.c | 9 ++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_dictviews.py b/Lib/test/test_dictviews.py index 7b16971facc6ec7..b15cfebc98912d5 100644 --- a/Lib/test/test_dictviews.py +++ b/Lib/test/test_dictviews.py @@ -109,8 +109,10 @@ def intersection(self, other): self.assertEqual(d4.keys() & d3.keys(), {'d'}) self.assertEqual(d4.keys() & set(d3.keys()), {'d'}) self.assertIsInstance(d4.keys() & frozenset(d3.keys()), set) - self.assertIsInstance(frozenset(d3.keys()) & d4.keys(), frozenset) + self.assertIsInstance(frozenset(d3.keys()) & d4.keys(), set) self.assertIs(type(d4.keys() & CustomSet(d3.keys())), set) + self.assertIs(type(d1.keys() & []), set) + self.assertIs(type([] & d1.keys()), set) self.assertEqual(d1.keys() | d1.keys(), {'a', 'b'}) self.assertEqual(d1.keys() | d2.keys(), {'a', 'b', 'c'}) diff --git a/Objects/dictobject.c b/Objects/dictobject.c index f2e8b69654e0c1d..4a591c296555d64 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -3935,14 +3935,13 @@ _PyDictView_Intersect(PyObject* self, PyObject *other) /* Python interpreter swaps parameters when dict view is on right side of & */ if (!PyDictViewSet_Check(self)) { - _Py_IDENTIFIER(intersection); - - return _PyObject_CallMethodIdObjArgs(self, &PyId_intersection, other, NULL); - } + PyObject *tmp = other; + other = self; + self = tmp; + } len_self = dictview_len((_PyDictViewObject *)self); - /* if other is a set and self is smaller than other, reuse set intersection logic */ if (Py_TYPE(other) == &PySet_Type && len_self <= PyObject_Size(other)) { From aebaea871168a5991bdd5c674c1d0d88999390af Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Sun, 25 Aug 2019 22:47:12 -0700 Subject: [PATCH 12/14] Fix review comments --- .../2018-06-14-13-55-45.bpo-27575.mMYgzv.rst | 2 +- Objects/dictobject.c | 17 +++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Misc/NEWS.d/next/Core and Builtins/2018-06-14-13-55-45.bpo-27575.mMYgzv.rst b/Misc/NEWS.d/next/Core and Builtins/2018-06-14-13-55-45.bpo-27575.mMYgzv.rst index 054276d666f89bc..2c250dcceb2820e 100644 --- a/Misc/NEWS.d/next/Core and Builtins/2018-06-14-13-55-45.bpo-27575.mMYgzv.rst +++ b/Misc/NEWS.d/next/Core and Builtins/2018-06-14-13-55-45.bpo-27575.mMYgzv.rst @@ -1,2 +1,2 @@ Improve speed of dictview intersection by directly using set intersection -logic. Patch by David Hsu. +logic. Patch by David Su. diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 4a591c296555d64..77a576b3b96c316 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -3922,7 +3922,7 @@ dictviews_sub(PyObject* self, PyObject *other) static int dictitems_contains(_PyDictViewObject *dv, PyObject *obj); -PyObject* +PyObject * _PyDictView_Intersect(PyObject* self, PyObject *other) { PyObject *result; @@ -3938,7 +3938,7 @@ _PyDictView_Intersect(PyObject* self, PyObject *other) PyObject *tmp = other; other = self; self = tmp; - } + } len_self = dictview_len((_PyDictViewObject *)self); @@ -3984,8 +3984,9 @@ _PyDictView_Intersect(PyObject* self, PyObject *other) while ((key = PyIter_Next(it)) != NULL) { rv = dict_contains((_PyDictViewObject *)self, key); - if (rv < 0) + if (rv < 0) { goto error; + } if (rv) { if (PySet_Add(result, key)) { goto error; @@ -4000,11 +4001,11 @@ _PyDictView_Intersect(PyObject* self, PyObject *other) } return result; - error: - Py_DECREF(it); - Py_DECREF(result); - Py_DECREF(key); - return NULL; +error: + Py_DECREF(it); + Py_DECREF(result); + Py_DECREF(key); + return NULL; } static PyObject* From 9b7b1fdf78e8431a6db11875bfe8655ad6458c45 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Sun, 25 Aug 2019 23:30:53 -0700 Subject: [PATCH 13/14] Fix merge --- Objects/dictobject.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 82ce3d169af89fa..217ea25d5b42300 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -4181,6 +4181,7 @@ _PyDictView_Intersect(PyObject* self, PyObject *other) Py_ssize_t len_self; int rv; int (*dict_contains)(_PyDictViewObject *, PyObject *); + PyObject *tmp; /* Python interpreter swaps parameters when dict view is on right side of & */ @@ -4196,7 +4197,6 @@ _PyDictView_Intersect(PyObject* self, PyObject *other) reuse set intersection logic */ if (Py_TYPE(other) == &PySet_Type && len_self <= PyObject_Size(other)) { _Py_IDENTIFIER(intersection); - return _PyObject_CallMethodIdObjArgs(other, &PyId_intersection, self, NULL); } @@ -4220,6 +4220,7 @@ _PyDictView_Intersect(PyObject* self, PyObject *other) it = PyObject_GetIter(other); + _Py_IDENTIFIER(intersection_update); tmp = _PyObject_CallMethodIdOneArg(result, &PyId_intersection_update, other); if (tmp == NULL) { Py_DECREF(result); From e1c01ae2d14a067703e153df4d64e1074fe6e210 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Sun, 25 Aug 2019 23:49:36 -0700 Subject: [PATCH 14/14] Fix reference counting --- Objects/dictobject.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 217ea25d5b42300..fec3a87e9f5de7c 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -4226,6 +4226,7 @@ _PyDictView_Intersect(PyObject* self, PyObject *other) Py_DECREF(result); return NULL; } + Py_DECREF(tmp); if (PyDictKeys_Check(self)) { dict_contains = dictkeys_contains;