Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 742da04

Browse files
committed
Implement compact dict
Issue #27350: `dict` implementation is changed like PyPy. It is more compact and preserves insertion order. _PyDict_Dummy() function has been removed. Disable test_gdb: python-gdb.py is not updated yet to the new structure of compact dictionaries (issue #28023). Patch written by INADA Naoki.
1 parent d8b7770 commit 742da04

12 files changed

Lines changed: 788 additions & 564 deletions

File tree

Doc/whatsnew/3.6.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,11 @@ Other Language Changes
343343

344344
Some smaller changes made to the core Python language are:
345345

346+
* `dict` implementation is changed like PyPy. It is more compact and preserves
347+
insertion order. :pep:`PEP 468` (Preserving the order of `**kwargs` in a
348+
function.) is implemented by this.
349+
(Contributed by INADA Naoki in :issue:`27350`.)
350+
346351
* Long sequences of repeated traceback lines are now abbreviated as
347352
``"[Previous line repeated {count} more times]"`` (see
348353
:ref:`py36-traceback` for an example).

Include/object.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -710,7 +710,6 @@ you can count such references to the type object.)
710710
PyAPI_DATA(Py_ssize_t) _Py_RefTotal;
711711
PyAPI_FUNC(void) _Py_NegativeRefcount(const char *fname,
712712
int lineno, PyObject *op);
713-
PyAPI_FUNC(PyObject *) _PyDict_Dummy(void);
714713
PyAPI_FUNC(Py_ssize_t) _Py_GetRefTotal(void);
715714
#define _Py_INC_REFTOTAL _Py_RefTotal++
716715
#define _Py_DEC_REFTOTAL _Py_RefTotal--

Lib/test/test_descr.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5116,12 +5116,14 @@ class B(A):
51165116
a, b = A(), B()
51175117
self.assertEqual(sys.getsizeof(vars(a)), sys.getsizeof(vars(b)))
51185118
self.assertLess(sys.getsizeof(vars(a)), sys.getsizeof({}))
5119-
a.x, a.y, a.z, a.w = range(4)
5119+
# Initial hash table can contain at most 5 elements.
5120+
# Set 6 attributes to cause internal resizing.
5121+
a.x, a.y, a.z, a.w, a.v, a.u = range(6)
51205122
self.assertNotEqual(sys.getsizeof(vars(a)), sys.getsizeof(vars(b)))
51215123
a2 = A()
51225124
self.assertEqual(sys.getsizeof(vars(a)), sys.getsizeof(vars(a2)))
51235125
self.assertLess(sys.getsizeof(vars(a)), sys.getsizeof({}))
5124-
b.u, b.v, b.w, b.t = range(4)
5126+
b.u, b.v, b.w, b.t, b.s, b.r = range(6)
51255127
self.assertLess(sys.getsizeof(vars(b)), sys.getsizeof({}))
51265128

51275129

Lib/test/test_gdb.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
import unittest
1212
import locale
1313

14+
# FIXME: issue #28023
15+
raise unittest.SkipTest("FIXME: issue #28023, compact dict (issue #27350) broke python-gdb.py")
16+
1417
# Is this Python configured to support threads?
1518
try:
1619
import _thread

Lib/test/test_ordered_dict.py

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import builtins
12
import contextlib
23
import copy
34
import gc
@@ -621,6 +622,25 @@ class PurePythonOrderedDictTests(OrderedDictTests, unittest.TestCase):
621622
OrderedDict = py_coll.OrderedDict
622623

623624

625+
class CPythonBuiltinDictTests(unittest.TestCase):
626+
"""Builtin dict preserves insertion order.
627+
628+
Reuse some of tests in OrderedDict selectively.
629+
"""
630+
631+
module = builtins
632+
OrderedDict = dict
633+
634+
for method in (
635+
"test_init test_update test_abc test_clear test_delitem " +
636+
"test_setitem test_detect_deletion_during_iteration " +
637+
"test_popitem test_reinsert test_override_update " +
638+
"test_highly_nested test_highly_nested_subclass " +
639+
"test_delitem_hash_collision ").split():
640+
setattr(CPythonBuiltinDictTests, method, getattr(OrderedDictTests, method))
641+
del method
642+
643+
624644
@unittest.skipUnless(c_coll, 'requires the C version of the collections module')
625645
class CPythonOrderedDictTests(OrderedDictTests, unittest.TestCase):
626646

@@ -635,18 +655,19 @@ def test_sizeof_exact(self):
635655
size = support.calcobjsize
636656
check = self.check_sizeof
637657

638-
basicsize = size('n2P' + '3PnPn2P') + calcsize('2nPn')
639-
entrysize = calcsize('n2P') + calcsize('P')
658+
basicsize = size('n2P' + '3PnPn2P') + calcsize('2nP2n')
659+
entrysize = calcsize('n2P')
660+
p = calcsize('P')
640661
nodesize = calcsize('Pn2P')
641662

642663
od = OrderedDict()
643-
check(od, basicsize + 8*entrysize)
664+
check(od, basicsize + 8*p + 8 + 5*entrysize) # 8byte indicies + 8*2//3 * entry table
644665
od.x = 1
645-
check(od, basicsize + 8*entrysize)
666+
check(od, basicsize + 8*p + 8 + 5*entrysize)
646667
od.update([(i, i) for i in range(3)])
647-
check(od, basicsize + 8*entrysize + 3*nodesize)
668+
check(od, basicsize + 8*p + 8 + 5*entrysize + 3*nodesize)
648669
od.update([(i, i) for i in range(3, 10)])
649-
check(od, basicsize + 16*entrysize + 10*nodesize)
670+
check(od, basicsize + 16*p + 16 + 10*entrysize + 10*nodesize)
650671

651672
check(od.keys(), size('P'))
652673
check(od.items(), size('P'))

Lib/test/test_sys.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -936,9 +936,9 @@ def inner():
936936
# method-wrapper (descriptor object)
937937
check({}.__iter__, size('2P'))
938938
# dict
939-
check({}, size('n2P') + calcsize('2nPn') + 8*calcsize('n2P'))
939+
check({}, size('n2P') + calcsize('2nP2n') + 8 + (8*2//3)*calcsize('n2P'))
940940
longdict = {1:1, 2:2, 3:3, 4:4, 5:5, 6:6, 7:7, 8:8}
941-
check(longdict, size('n2P') + calcsize('2nPn') + 16*calcsize('n2P'))
941+
check(longdict, size('n2P') + calcsize('2nP2n') + 16 + (16*2//3)*calcsize('n2P'))
942942
# dictionary-keyview
943943
check({}.keys(), size('P'))
944944
# dictionary-valueview
@@ -1096,13 +1096,13 @@ def delx(self): del self.__x
10961096
'10P' # PySequenceMethods
10971097
'2P' # PyBufferProcs
10981098
'4P')
1099-
# Separate block for PyDictKeysObject with 4 entries
1100-
s += calcsize("2nPn") + 4*calcsize("n2P")
1099+
# Separate block for PyDictKeysObject with 8 keys and 5 entries
1100+
s += calcsize("2nP2n") + 8 + 5*calcsize("n2P")
11011101
# class
11021102
class newstyleclass(object): pass
11031103
check(newstyleclass, s)
11041104
# dict with shared keys
1105-
check(newstyleclass().__dict__, size('n2P' + '2nPn'))
1105+
check(newstyleclass().__dict__, size('n2P' + '2nP2n'))
11061106
# unicode
11071107
# each tuple contains a string and its expected character size
11081108
# don't put any static strings here, as they may contain

Lib/test/test_weakref.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1325,13 +1325,16 @@ def check_weak_del_and_len_while_iterating(self, dict, testcontext):
13251325
o = Object(123456)
13261326
with testcontext():
13271327
n = len(dict)
1328-
dict.popitem()
1328+
# Since underlaying dict is ordered, first item is popped
1329+
dict.pop(next(dict.keys()))
13291330
self.assertEqual(len(dict), n - 1)
13301331
dict[o] = o
13311332
self.assertEqual(len(dict), n)
1333+
# last item in objects is removed from dict in context shutdown
13321334
with testcontext():
13331335
self.assertEqual(len(dict), n - 1)
1334-
dict.pop(next(dict.keys()))
1336+
# Then, (o, o) is popped
1337+
dict.popitem()
13351338
self.assertEqual(len(dict), n - 2)
13361339
with testcontext():
13371340
self.assertEqual(len(dict), n - 3)

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ What's New in Python 3.6.0 beta 1
1010
Core and Builtins
1111
-----------------
1212

13+
- Issue #27350: `dict` implementation is changed like PyPy. It is more compact
14+
and preserves insertion order.
15+
1316
- Issue #27911: Remove unnecessary error checks in
1417
``exec_builtin_or_dynamic()``.
1518

Objects/dict-common.h

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,25 @@ typedef struct {
88
PyObject *me_value; /* This field is only meaningful for combined tables */
99
} PyDictKeyEntry;
1010

11-
typedef PyDictKeyEntry *(*dict_lookup_func)
12-
(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject ***value_addr);
11+
/* dict_lookup_func() returns index of entry which can be used like DK_ENTRIES(dk)[index].
12+
* -1 when no entry found, -3 when compare raises error.
13+
*/
14+
typedef Py_ssize_t (*dict_lookup_func)
15+
(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject ***value_addr,
16+
Py_ssize_t *hashpos);
1317

18+
#define DKIX_EMPTY (-1)
19+
#define DKIX_DUMMY (-2) /* Used internally */
20+
#define DKIX_ERROR (-3)
21+
22+
/* See dictobject.c for actual layout of DictKeysObject */
1423
struct _dictkeysobject {
1524
Py_ssize_t dk_refcnt;
1625
Py_ssize_t dk_size;
1726
dict_lookup_func dk_lookup;
1827
Py_ssize_t dk_usable;
19-
PyDictKeyEntry dk_entries[1];
28+
Py_ssize_t dk_nentries; /* How many entries are used. */
29+
char dk_indices[8]; /* dynamically sized. 8 is minimum. */
2030
};
2131

2232
#endif

0 commit comments

Comments
 (0)