pFad - Phone/Frame/Anonymizer/Declutterfier! Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

URL: http://github.com/python/cpython/commit/55bc3126e0a09a8e940da73e51ab1ffeecaf02d4

tylesheet" href="https://github.githubassets.com/assets/global-d18f184ea1a06a2c.css" /> gh-151722: Do not track the frozendict in the GC in _PyDict_FromKeys(… · python/cpython@55bc312 · GitHub
Skip to content

Commit 55bc312

Browse files
vstinnercorona10methane
authored
gh-151722: Do not track the frozendict in the GC in _PyDict_FromKeys() (#152067)
_PyDict_FromKeys() now creates a frozendict copy which is not tracked by the GC. dict_merge() no longer requires the dictionary to be tracked by the GC. Co-authored-by: Donghee Na <donghee.na@python.org> Co-authored-by: Inada Naoki <songofacandy@gmail.com>
1 parent bef5706 commit 55bc312

4 files changed

Lines changed: 149 additions & 65 deletions

File tree

Doc/library/stdtypes.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5757,6 +5757,13 @@ Frozen dictionaries
57575757
Like dictionaries, frozendicts are :ref:`generic <generics>` over two types,
57585758
signifying (respectively) the types of the frozendict's keys and values.
57595759

5760+
.. classmethod:: fromkeys(iterable, value=None, /)
5761+
5762+
Similar to :meth:`dict.fromkeys`, but call again the type constructor
5763+
with an initialized :class:`frozendict` if the type is a
5764+
:class:`frozendict` subclass or if the constructor returned a
5765+
:class:`frozendict`.
5766+
57605767
.. versionadded:: 3.15
57615768

57625769

Lib/test/test_dict.py

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1939,8 +1939,11 @@ def test_fromkeys(self):
19391939
# Subclass which overrides the constructor
19401940
created = frozendict(x=1)
19411941
class FrozenDictSubclass(frozendict):
1942-
def __new__(self):
1943-
return created
1942+
def __new__(cls, *args, **kwargs):
1943+
if args or kwargs:
1944+
return super().__new__(cls, *args, **kwargs)
1945+
else:
1946+
return created
19441947

19451948
fd = FrozenDictSubclass.fromkeys("abc")
19461949
self.assertEqual(fd, frozendict(x=1, a=None, b=None, c=None))
@@ -1952,6 +1955,20 @@ def __new__(self):
19521955
self.assertEqual(type(fd), FrozenDictSubclass)
19531956
self.assertEqual(created, frozendict(x=1))
19541957

1958+
# Dict subclass with a constructor which returns a frozendict
1959+
# by default
1960+
class DictSubclass(dict):
1961+
def __new__(cls, *args, **kwargs):
1962+
if args or kwargs:
1963+
return super().__new__(cls, *args, **kwargs)
1964+
else:
1965+
return created
1966+
1967+
fd = DictSubclass.fromkeys("abc")
1968+
self.assertEqual(fd, frozendict(x=1, a=None, b=None, c=None))
1969+
self.assertEqual(type(fd), DictSubclass)
1970+
self.assertEqual(created, frozendict(x=1))
1971+
19551972
# Subclass which doesn't override the constructor
19561973
class FrozenDictSubclass2(frozendict):
19571974
pass
@@ -1960,16 +1977,6 @@ class FrozenDictSubclass2(frozendict):
19601977
self.assertEqual(fd, frozendict(a=None, b=None, c=None))
19611978
self.assertEqual(type(fd), FrozenDictSubclass2)
19621979

1963-
# Dict subclass which overrides the constructor
1964-
class DictSubclass(dict):
1965-
def __new__(self):
1966-
return created
1967-
1968-
fd = DictSubclass.fromkeys("abc")
1969-
self.assertEqual(fd, frozendict(x=1, a=None, b=None, c=None))
1970-
self.assertEqual(type(fd), DictSubclass)
1971-
self.assertEqual(created, frozendict(x=1))
1972-
19731980
def test_pickle(self):
19741981
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
19751982
for fd in (
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:meth:`!frozendict.fromkeys` now only tracks the :class:`frozendict` in the
2+
garbage collector once the dictionary is fully initialized. Patch by Donghee Na
3+
and Victor Stinner.

Objects/dictobject.c

Lines changed: 120 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ static PyObject* frozendict_new(PyTypeObject *type, PyObject *args,
140140
PyObject *kwds);
141141
static PyObject* frozendict_new_untracked(PyTypeObject *type);
142142
static PyObject* dict_new(PyTypeObject *type, PyObject *args, PyObject *kwds);
143+
static PyObject* dict_new_untracked(PyTypeObject *type);
143144
static int dict_merge(PyObject *a, PyObject *b, int override, PyObject **dupkey);
144145
static int dict_contains(PyObject *op, PyObject *key);
145146
static int dict_merge_from_seq2(PyObject *d, PyObject *seq2, int override);
@@ -3431,40 +3432,47 @@ dict_set_fromkeys(PyDictObject *mp, PyObject *iterable, PyObject *value)
34313432
PyObject *
34323433
_PyDict_FromKeys(PyObject *cls, PyObject *iterable, PyObject *value)
34333434
{
3434-
PyObject *it; /* iter(iterable) */
3435-
PyObject *key;
3435+
PyObject *it = NULL; /* iter(iterable) */
34363436
PyObject *d;
3437-
int status;
3437+
int need_copy = 0;
34383438

3439-
d = _PyObject_CallNoArgs(cls);
3439+
if (cls == (PyObject*)&PyFrozenDict_Type) {
3440+
// gh-151722: Create a frozendict which is not tracked by the GC.
3441+
d = frozendict_new_untracked(&PyFrozenDict_Type);
3442+
}
3443+
else {
3444+
// Dict subclass, or frozendict subclass which overrides
3445+
// the constructor.
3446+
d = _PyObject_CallNoArgs(cls);
3447+
}
34403448
if (d == NULL) {
34413449
return NULL;
34423450
}
34433451

3444-
// If cls is a dict or frozendict subclass with overridden constructor,
3445-
// copy the frozendict.
3446-
PyTypeObject *cls_type = _PyType_CAST(cls);
3447-
if (PyFrozenDict_Check(d) && cls_type->tp_new != frozendict_new) {
3448-
// Subclass-friendly copy
3449-
PyObject *copy;
3450-
if (PyObject_IsSubclass(cls, (PyObject*)&PyFrozenDict_Type)) {
3451-
copy = frozendict_new(cls_type, NULL, NULL);
3452-
}
3453-
else {
3454-
copy = dict_new(cls_type, NULL, NULL);
3455-
}
3452+
// gh-151722: If cls constructor returns a frozendict which is tracked by
3453+
// the GC, create a frozendict copy which is not tracked by the GC.
3454+
//
3455+
// At the function exit, return cls(fd) where fd is a frozendict.
3456+
//
3457+
// Untracking the frozendict requires tracking again the frozendict on
3458+
// error which is more complicated. It's easier to work on a copy.
3459+
if (PyFrozenDict_Check(d) && _PyObject_GC_IS_TRACKED(d)) {
3460+
need_copy = 1;
3461+
3462+
PyObject *copy = frozendict_new_untracked(&PyFrozenDict_Type);
34563463
if (copy == NULL) {
3457-
Py_DECREF(d);
3458-
return NULL;
3464+
goto Fail;
34593465
}
34603466
if (dict_merge(copy, d, 1, NULL) < 0) {
3461-
Py_DECREF(d);
34623467
Py_DECREF(copy);
3463-
return NULL;
3468+
goto Fail;
34643469
}
34653470
Py_SETREF(d, copy);
34663471
}
3467-
assert(!PyFrozenDict_Check(d) || can_modify_dict((PyDictObject*)d));
3472+
if (PyFrozenDict_Check(d)) {
3473+
assert(can_modify_dict((PyDictObject*)d));
3474+
assert(!_PyObject_GC_IS_TRACKED(d));
3475+
}
34683476

34693477
if (PyDict_CheckExact(d)) {
34703478
if (PyDict_CheckExact(iterable)) {
@@ -3473,23 +3481,23 @@ _PyDict_FromKeys(PyObject *cls, PyObject *iterable, PyObject *value)
34733481
Py_BEGIN_CRITICAL_SECTION2(d, iterable);
34743482
d = (PyObject *)dict_dict_fromkeys(mp, iterable, value);
34753483
Py_END_CRITICAL_SECTION2();
3476-
return d;
3484+
goto Done;
34773485
}
34783486
else if (PyFrozenDict_CheckExact(iterable)) {
34793487
PyDictObject *mp = (PyDictObject *)d;
34803488

34813489
Py_BEGIN_CRITICAL_SECTION(d);
34823490
d = (PyObject *)dict_dict_fromkeys(mp, iterable, value);
34833491
Py_END_CRITICAL_SECTION();
3484-
return d;
3492+
goto Done;
34853493
}
34863494
else if (PyAnySet_CheckExact(iterable)) {
34873495
PyDictObject *mp = (PyDictObject *)d;
34883496

34893497
Py_BEGIN_CRITICAL_SECTION2(d, iterable);
34903498
d = (PyObject *)dict_set_fromkeys(mp, iterable, value);
34913499
Py_END_CRITICAL_SECTION2();
3492-
return d;
3500+
goto Done;
34933501
}
34943502
}
34953503
else if (PyFrozenDict_CheckExact(d)) {
@@ -3499,71 +3507,113 @@ _PyDict_FromKeys(PyObject *cls, PyObject *iterable, PyObject *value)
34993507
Py_BEGIN_CRITICAL_SECTION(iterable);
35003508
d = (PyObject *)dict_dict_fromkeys(mp, iterable, value);
35013509
Py_END_CRITICAL_SECTION();
3502-
return d;
3510+
goto Done;
35033511
}
35043512
else if (PyFrozenDict_CheckExact(iterable)) {
35053513
PyDictObject *mp = (PyDictObject *)d;
35063514
d = (PyObject *)dict_dict_fromkeys(mp, iterable, value);
3507-
return d;
3515+
goto Done;
35083516
}
35093517
else if (PyAnySet_CheckExact(iterable)) {
35103518
PyDictObject *mp = (PyDictObject *)d;
35113519

35123520
Py_BEGIN_CRITICAL_SECTION(iterable);
35133521
d = (PyObject *)dict_set_fromkeys(mp, iterable, value);
35143522
Py_END_CRITICAL_SECTION();
3515-
return d;
3523+
goto Done;
35163524
}
35173525
}
35183526

35193527
it = PyObject_GetIter(iterable);
35203528
if (it == NULL){
3521-
Py_DECREF(d);
3522-
return NULL;
3529+
goto Fail;
35233530
}
35243531

35253532
if (PyDict_CheckExact(d)) {
3533+
int status = 0;
3534+
35263535
Py_BEGIN_CRITICAL_SECTION(d);
3527-
while ((key = PyIter_Next(it)) != NULL) {
3536+
while (1) {
3537+
PyObject *key;
3538+
status = PyIter_NextItem(it, &key);
3539+
if (status <= 0) {
3540+
break;
3541+
}
3542+
35283543
status = setitem_lock_held((PyDictObject *)d, key, value);
35293544
Py_DECREF(key);
35303545
if (status < 0) {
3531-
assert(PyErr_Occurred());
3532-
goto dict_iter_exit;
3546+
break;
35333547
}
35343548
}
3535-
dict_iter_exit:;
35363549
Py_END_CRITICAL_SECTION();
3550+
3551+
if (status < 0) {
3552+
goto Fail;
3553+
}
35373554
}
35383555
else if (PyFrozenDict_Check(d)) {
3539-
while ((key = PyIter_Next(it)) != NULL) {
3556+
while (1) {
3557+
PyObject *key;
3558+
int status = PyIter_NextItem(it, &key);
3559+
if (status < 0) {
3560+
goto Fail;
3561+
}
3562+
if (status == 0) {
3563+
break;
3564+
}
3565+
35403566
// setitem_take2_lock_held consumes a reference to key
35413567
status = setitem_take2_lock_held((PyDictObject *)d,
35423568
key, Py_NewRef(value));
35433569
if (status < 0) {
3544-
assert(PyErr_Occurred());
35453570
goto Fail;
35463571
}
35473572
}
35483573
}
35493574
else {
3550-
while ((key = PyIter_Next(it)) != NULL) {
3575+
while (1) {
3576+
PyObject *key;
3577+
int status = PyIter_NextItem(it, &key);
3578+
if (status < 0) {
3579+
goto Fail;
3580+
}
3581+
if (status == 0) {
3582+
break;
3583+
}
3584+
35513585
status = PyObject_SetItem(d, key, value);
35523586
Py_DECREF(key);
3553-
if (status < 0)
3587+
if (status < 0) {
35543588
goto Fail;
3589+
}
35553590
}
3591+
35563592
}
35573593

3558-
if (PyErr_Occurred())
3559-
goto Fail;
3594+
assert(!PyErr_Occurred());
35603595
Py_DECREF(it);
3561-
return d;
3596+
goto Done;
35623597

35633598
Fail:
3564-
Py_DECREF(it);
3599+
assert(PyErr_Occurred());
3600+
Py_XDECREF(it);
35653601
Py_DECREF(d);
35663602
return NULL;
3603+
3604+
Done:
3605+
if (d == NULL) {
3606+
return NULL;
3607+
}
3608+
3609+
if (need_copy) {
3610+
PyObject *copy = _PyObject_CallOneArg(cls, d);
3611+
Py_SETREF(d, copy);
3612+
}
3613+
else if (!_PyObject_GC_IS_TRACKED(d)) {
3614+
_PyObject_GC_TRACK(d);
3615+
}
3616+
return d;
35673617
}
35683618

35693619
/* Methods */
@@ -4164,9 +4214,6 @@ dict_dict_merge(PyDictObject *mp, PyDictObject *other, int override, PyObject **
41644214
set_keys(mp, keys);
41654215
STORE_USED(mp, other->ma_used);
41664216
ASSERT_CONSISTENT(mp);
4167-
if (PyDict_Check(mp)) {
4168-
assert(_PyObject_GC_IS_TRACKED(mp));
4169-
}
41704217
return 0;
41714218
}
41724219
}
@@ -4333,7 +4380,12 @@ dict_merge_api(PyObject *a, PyObject *b, int override, PyObject **dupkey)
43334380
}
43344381
return -1;
43354382
}
4336-
return dict_merge(a, b, override, dupkey);
4383+
4384+
int res = dict_merge(a, b, override, dupkey);
4385+
if (PyDict_Check(a)) {
4386+
assert(_PyObject_GC_IS_TRACKED(a));
4387+
}
4388+
return res;
43374389
}
43384390

43394391
int
@@ -4492,10 +4544,15 @@ copy_lock_held(PyObject *o, int as_frozendict)
44924544
}
44934545
if (copy == NULL)
44944546
return NULL;
4495-
if (dict_merge(copy, o, 1, NULL) == 0)
4496-
return copy;
4497-
Py_DECREF(copy);
4498-
return NULL;
4547+
if (dict_merge(copy, o, 1, NULL) < 0) {
4548+
Py_DECREF(copy);
4549+
return NULL;
4550+
}
4551+
4552+
if (PyDict_Check(copy)) {
4553+
assert(_PyObject_GC_IS_TRACKED(copy));
4554+
}
4555+
return copy;
44994556
}
45004557

45014558
PyObject *
@@ -5256,11 +5313,11 @@ static PyNumberMethods dict_as_number = {
52565313
.nb_inplace_or = _PyDict_IOr,
52575314
};
52585315

5259-
static PyObject *
5260-
dict_new_untracked(PyTypeObject *type)
5316+
static PyObject*
5317+
anydict_new_untracked(PyTypeObject *type)
52615318
{
52625319
assert(type != NULL);
5263-
// dict subclasses must implement the GC protocol
5320+
// dict and frozendict subclasses must implement the GC protocol
52645321
assert(_PyType_IS_GC(type));
52655322

52665323
PyObject *self = _PyType_AllocNoTrack(type, 0);
@@ -5279,6 +5336,14 @@ dict_new_untracked(PyTypeObject *type)
52795336
return self;
52805337
}
52815338

5339+
static PyObject*
5340+
dict_new_untracked(PyTypeObject *type)
5341+
{
5342+
assert(PyObject_IsSubclass((PyObject*)type, (PyObject*)&PyDict_Type));
5343+
5344+
return anydict_new_untracked(type);
5345+
}
5346+
52825347
static PyObject *
52835348
dict_new(PyTypeObject *type, PyObject *Py_UNUSED(args), PyObject *Py_UNUSED(kwds))
52845349
{
@@ -8394,7 +8459,9 @@ frozendict_hash(PyObject *op)
83948459
static PyObject *
83958460
frozendict_new_untracked(PyTypeObject *type)
83968461
{
8397-
PyObject *d = dict_new_untracked(type);
8462+
assert(PyObject_IsSubclass((PyObject*)type, (PyObject*)&PyFrozenDict_Type));
8463+
8464+
PyObject *d = anydict_new_untracked(type);
83988465
if (d == NULL) {
83998466
return NULL;
84008467
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad © 2024 Your Company Name. All rights reserved.





Check this box to remove all script contents from the fetched content.



Check this box to remove all images from the fetched content.


Check this box to remove all CSS styles from the fetched content.


Check this box to keep images inefficiently compressed and original size.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy