Skip to content

Commit 5ba97b6

Browse files
committed
gh-141510: Add PyFrozenDict_AsDict() function
* PyDict_Copy() no longer accepts frozendict. * Remove _PyDict_CopyAsDict() function. * Fix frozendict.items() ^ frozendict.items(). Add non-regression test.
1 parent 0eaf260 commit 5ba97b6

File tree

9 files changed

+115
-40
lines changed

9 files changed

+115
-40
lines changed

Doc/c-api/dict.rst

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,6 @@ Dictionary objects
8282
8383
Return a new dictionary that contains the same key-value pairs as *p*.
8484
85-
.. versionchanged:: next
86-
If *p* is a subclass of :class:`frozendict`, the result will be a
87-
:class:`frozendict` instance instead of a :class:`dict` instance.
88-
8985
.. c:function:: int PyDict_SetItem(PyObject *p, PyObject *key, PyObject *val)
9086
9187
Insert *val* into the dictionary *p* with a key of *key*. *key* must be
@@ -546,6 +542,10 @@ Frozen dictionary objects
546542
547543
Create an empty dictionary if *iterable* is ``NULL``.
548544
545+
.. c:function:: PyObject* PyFrozenDict_AsDict(PyObject *p)
546+
547+
Convert a :class:`frozendict` to a :class:`dict` (create a copy).
548+
549549
550550
Ordered dictionaries
551551
^^^^^^^^^^^^^^^^^^^^

Include/cpython/dictobject.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,6 @@ PyAPI_FUNC(int) PyDict_Unwatch(int watcher_id, PyObject* dict);
106106

107107
// Create a frozendict. Create an empty dictionary if iterable is NULL.
108108
PyAPI_FUNC(PyObject*) PyFrozenDict_New(PyObject *iterable);
109+
110+
// Convert a frozendict to a dict (create a copy).
111+
PyAPI_FUNC(PyObject*) PyFrozenDict_AsDict(PyObject *o);

Include/internal/pycore_dict.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,6 @@ extern void _PyDict_Clear_LockHeld(PyObject *op);
160160
PyAPI_FUNC(void) _PyDict_EnsureSharedOnRead(PyDictObject *mp);
161161
#endif
162162

163-
extern PyObject* _PyDict_CopyAsDict(PyObject *op);
164-
165163
#define DKIX_EMPTY (-1)
166164
#define DKIX_DUMMY (-2) /* Used internally */
167165
#define DKIX_ERROR (-3)

Lib/test/test_capi/test_dict.py

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -97,21 +97,13 @@ def test_dictproxy_new(self):
9797
def test_dict_copy(self):
9898
# Test PyDict_Copy()
9999
copy = _testlimitedcapi.dict_copy
100-
for dict_type in ANYDICT_TYPES:
100+
for dict_type in DICT_TYPES:
101101
dct = dict_type({1: 2})
102102
dct_copy = copy(dct)
103-
if dict_type == frozendict:
104-
expected_type = frozendict
105-
self.assertIs(dct_copy, dct)
106-
else:
107-
if issubclass(dict_type, frozendict):
108-
expected_type = frozendict
109-
else:
110-
expected_type = dict
111-
self.assertIs(type(dct_copy), expected_type)
112-
self.assertEqual(dct_copy, dct)
103+
self.assertIs(type(dct_copy), dict)
104+
self.assertEqual(dct_copy, dct)
113105

114-
for test_type in NOT_ANYDICT_TYPES + OTHER_TYPES:
106+
for test_type in NOT_DICT_TYPES + OTHER_TYPES:
115107
self.assertRaises(SystemError, copy, test_type())
116108
self.assertRaises(SystemError, copy, NULL)
117109

@@ -632,6 +624,19 @@ def test_frozendict_new(self):
632624
self.assertEqual(dct, frozendict())
633625
self.assertIs(type(dct), frozendict)
634626

627+
def test_frozendict_asdict(self):
628+
# Test PyFrozenDict_AsDict()
629+
frozendict_asdict = _testlimitedcapi.frozendict_asdict
630+
for dict_type in FROZENDICT_TYPES:
631+
dct = dict_type({1: 2})
632+
dct_copy = frozendict_asdict(dct)
633+
self.assertIs(type(dct_copy), dict)
634+
self.assertEqual(dct_copy, dct)
635+
636+
for test_type in NOT_FROZENDICT_TYPES + OTHER_TYPES:
637+
self.assertRaises(SystemError, frozendict_asdict, test_type())
638+
self.assertRaises(SystemError, frozendict_asdict, NULL)
639+
635640

636641
if __name__ == "__main__":
637642
unittest.main()

Lib/test/test_dict.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1848,11 +1848,19 @@ def test_merge(self):
18481848
frozendict({'x': 1, 'y': 2}))
18491849
self.assertEqual(frozendict(x=1, y=2) | frozendict(y=5),
18501850
frozendict({'x': 1, 'y': 5}))
1851+
self.assertEqual(FrozenDict(x=1, y=2) | FrozenDict(y=5),
1852+
frozendict({'x': 1, 'y': 5}))
1853+
18511854
fd = frozendict(x=1, y=2)
18521855
self.assertIs(fd | frozendict(), fd)
18531856
self.assertIs(fd | {}, fd)
18541857
self.assertIs(frozendict() | fd, fd)
18551858

1859+
fd = FrozenDict(x=1, y=2)
1860+
self.assertEqual(fd | frozendict(), fd)
1861+
self.assertEqual(fd | {}, fd)
1862+
self.assertEqual(frozendict() | fd, fd)
1863+
18561864
def test_update(self):
18571865
# test "a |= b" operator
18581866
d = frozendict(x=1)
@@ -1863,6 +1871,11 @@ def test_update(self):
18631871
self.assertEqual(d, frozendict({'x': 1, 'y': 2}))
18641872
self.assertEqual(copy, frozendict({'x': 1}))
18651873

1874+
def test_items_xor(self):
1875+
# test "a ^ b" operator on items views
1876+
res = frozendict(a=1, b=2).items() ^ frozendict(b=2, c=3).items()
1877+
self.assertEqual(res, {('a', 1), ('c', 3)})
1878+
18661879
def test_repr(self):
18671880
d = frozendict()
18681881
self.assertEqual(repr(d), "frozendict()")

Modules/_testcapi/dict.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,14 @@ frozendict_new(PyObject *self, PyObject *obj)
295295
}
296296

297297

298+
static PyObject*
299+
frozendict_asdict(PyObject *self, PyObject *obj)
300+
{
301+
NULLABLE(obj);
302+
return PyFrozenDict_AsDict(obj);
303+
}
304+
305+
298306
static PyMethodDef test_methods[] = {
299307
{"dict_containsstring", dict_containsstring, METH_VARARGS},
300308
{"dict_getitemref", dict_getitemref, METH_VARARGS},
@@ -311,6 +319,7 @@ static PyMethodDef test_methods[] = {
311319
{"anydict_check", anydict_check, METH_O},
312320
{"anydict_checkexact", anydict_checkexact, METH_O},
313321
{"frozendict_new", frozendict_new, METH_O},
322+
{"frozendict_asdict", frozendict_asdict, METH_O},
314323
{NULL},
315324
};
316325

Objects/clinic/dictobject.c.h

Lines changed: 19 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Objects/dictobject.c

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,9 @@ static int dict_merge_from_seq2(PyObject *d, PyObject *seq2, int override);
146146

147147
/*[clinic input]
148148
class dict "PyDictObject *" "&PyDict_Type"
149+
class frozendict "PyFrozenDictObject *" "&PyFrozenDict_Type"
149150
[clinic start generated code]*/
150-
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=f157a5a0ce9589d6]*/
151+
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=5dfa93bac68e7c54]*/
151152

152153

153154
/*
@@ -4384,7 +4385,23 @@ copy_lock_held(PyObject *o, int as_frozendict)
43844385
return NULL;
43854386
}
43864387

4387-
// Similar to PyDict_Copy(), but copy also frozendict.
4388+
PyObject *
4389+
PyDict_Copy(PyObject *o)
4390+
{
4391+
if (o == NULL || !PyDict_Check(o)) {
4392+
PyErr_BadInternalCall();
4393+
return NULL;
4394+
}
4395+
4396+
PyObject *res;
4397+
Py_BEGIN_CRITICAL_SECTION(o);
4398+
res = copy_lock_held(o, 0);
4399+
Py_END_CRITICAL_SECTION();
4400+
return res;
4401+
}
4402+
4403+
// Similar to PyDict_Copy(), but return a frozendict if the argument
4404+
// is a frozendict.
43884405
static PyObject *
43894406
_PyDict_Copy(PyObject *o)
43904407
{
@@ -4397,27 +4414,14 @@ _PyDict_Copy(PyObject *o)
43974414
return res;
43984415
}
43994416

4400-
PyObject *
4401-
PyDict_Copy(PyObject *o)
4417+
PyObject*
4418+
PyFrozenDict_AsDict(PyObject *o)
44024419
{
4403-
if (o == NULL || !PyAnyDict_Check(o)) {
4420+
if (o == NULL || !PyFrozenDict_Check(o)) {
44044421
PyErr_BadInternalCall();
44054422
return NULL;
44064423
}
44074424

4408-
if (PyFrozenDict_CheckExact(o)) {
4409-
return Py_NewRef(o);
4410-
}
4411-
4412-
return _PyDict_Copy(o);
4413-
}
4414-
4415-
// Similar to PyDict_Copy(), but return a dict if the argument is a frozendict.
4416-
PyObject*
4417-
_PyDict_CopyAsDict(PyObject *o)
4418-
{
4419-
assert(PyAnyDict_Check(o));
4420-
44214425
PyObject *res;
44224426
Py_BEGIN_CRITICAL_SECTION(o);
44234427
res = copy_lock_held(o, 0);
@@ -6523,7 +6527,7 @@ dictitems_xor_lock_held(PyObject *d1, PyObject *d2)
65236527
ASSERT_DICT_LOCKED(d1);
65246528
ASSERT_DICT_LOCKED(d2);
65256529

6526-
PyObject *temp_dict = copy_lock_held(d1, PyFrozenDict_Check(d1));
6530+
PyObject *temp_dict = copy_lock_held(d1, 0);
65276531
if (temp_dict == NULL) {
65286532
return NULL;
65296533
}
@@ -8057,7 +8061,7 @@ static PyMethodDef frozendict_methods[] = {
80578061
DICT_ITEMS_METHODDEF
80588062
DICT_VALUES_METHODDEF
80598063
DICT_FROMKEYS_METHODDEF
8060-
DICT_COPY_METHODDEF
8064+
FROZENDICT_COPY_METHODDEF
80618065
DICT___REVERSED___METHODDEF
80628066
{"__class_getitem__", Py_GenericAlias, METH_O|METH_CLASS, PyDoc_STR("See PEP 585")},
80638067
{"__getnewargs__", frozendict_getnewargs, METH_NOARGS},
@@ -8182,6 +8186,25 @@ PyFrozenDict_New(PyObject *iterable)
81828186
}
81838187
}
81848188

8189+
/*[clinic input]
8190+
frozendict.copy
8191+
8192+
Return a shallow copy of the frozendict.
8193+
[clinic start generated code]*/
8194+
8195+
static PyObject *
8196+
frozendict_copy_impl(PyFrozenDictObject *self)
8197+
/*[clinic end generated code: output=e580fd91d9fc2cf7 input=35f6abeaa08fd4bc]*/
8198+
{
8199+
assert(PyFrozenDict_Check(self));
8200+
8201+
if (PyFrozenDict_CheckExact(self)) {
8202+
return Py_NewRef(self);
8203+
}
8204+
8205+
return _PyDict_Copy((PyObject*)self);
8206+
}
8207+
81858208

81868209
PyTypeObject PyFrozenDict_Type = {
81878210
PyVarObject_HEAD_INIT(&PyType_Type, 0)

Objects/typeobject.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4850,7 +4850,13 @@ type_new_get_slots(type_new_ctx *ctx, PyObject *dict)
48504850
static PyTypeObject*
48514851
type_new_init(type_new_ctx *ctx)
48524852
{
4853-
PyObject *dict = _PyDict_CopyAsDict(ctx->orig_dict);
4853+
PyObject *dict;
4854+
if (PyFrozenDict_Check(ctx->orig_dict)) {
4855+
dict = PyFrozenDict_AsDict(ctx->orig_dict);
4856+
}
4857+
else {
4858+
dict = PyDict_Copy(ctx->orig_dict);
4859+
}
48544860
if (dict == NULL) {
48554861
goto error;
48564862
}

0 commit comments

Comments
 (0)