Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions Doc/c-api/dict.rst
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,6 @@ Dictionary objects

Return a new dictionary that contains the same key-value pairs as *p*.

.. versionchanged:: next
If *p* is a subclass of :class:`frozendict`, the result will be a
:class:`frozendict` instance instead of a :class:`dict` instance.

.. c:function:: int PyDict_SetItem(PyObject *p, PyObject *key, PyObject *val)

Insert *val* into the dictionary *p* with a key of *key*. *key* must be
Expand Down Expand Up @@ -546,6 +542,10 @@ Frozen dictionary objects

Create an empty dictionary if *iterable* is ``NULL``.

.. c:function:: PyObject* PyFrozenDict_AsDict(PyObject *p)

Convert a :class:`frozendict` to a :class:`dict` (create a copy).


Ordered dictionaries
^^^^^^^^^^^^^^^^^^^^
Expand Down
3 changes: 3 additions & 0 deletions Include/cpython/dictobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,6 @@ PyAPI_FUNC(int) PyDict_Unwatch(int watcher_id, PyObject* dict);

// Create a frozendict. Create an empty dictionary if iterable is NULL.
PyAPI_FUNC(PyObject*) PyFrozenDict_New(PyObject *iterable);

// Convert a frozendict to a dict (create a copy).
PyAPI_FUNC(PyObject*) PyFrozenDict_AsDict(PyObject *o);
2 changes: 0 additions & 2 deletions Include/internal/pycore_dict.h
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,6 @@ extern void _PyDict_Clear_LockHeld(PyObject *op);
PyAPI_FUNC(void) _PyDict_EnsureSharedOnRead(PyDictObject *mp);
#endif

extern PyObject* _PyDict_CopyAsDict(PyObject *op);

#define DKIX_EMPTY (-1)
#define DKIX_DUMMY (-2) /* Used internally */
#define DKIX_ERROR (-3)
Expand Down
29 changes: 17 additions & 12 deletions Lib/test/test_capi/test_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,21 +97,13 @@ def test_dictproxy_new(self):
def test_dict_copy(self):
# Test PyDict_Copy()
copy = _testlimitedcapi.dict_copy
for dict_type in ANYDICT_TYPES:
for dict_type in DICT_TYPES:
dct = dict_type({1: 2})
dct_copy = copy(dct)
if dict_type == frozendict:
expected_type = frozendict
self.assertIs(dct_copy, dct)
else:
if issubclass(dict_type, frozendict):
expected_type = frozendict
else:
expected_type = dict
self.assertIs(type(dct_copy), expected_type)
self.assertEqual(dct_copy, dct)
self.assertIs(type(dct_copy), dict)
self.assertEqual(dct_copy, dct)

for test_type in NOT_ANYDICT_TYPES + OTHER_TYPES:
for test_type in NOT_DICT_TYPES + OTHER_TYPES:
self.assertRaises(SystemError, copy, test_type())
self.assertRaises(SystemError, copy, NULL)

Expand Down Expand Up @@ -632,6 +624,19 @@ def test_frozendict_new(self):
self.assertEqual(dct, frozendict())
self.assertIs(type(dct), frozendict)

def test_frozendict_asdict(self):
# Test PyFrozenDict_AsDict()
frozendict_asdict = _testlimitedcapi.frozendict_asdict
for dict_type in FROZENDICT_TYPES:
dct = dict_type({1: 2})
dct_copy = frozendict_asdict(dct)
self.assertIs(type(dct_copy), dict)
self.assertEqual(dct_copy, dct)

for test_type in NOT_FROZENDICT_TYPES + OTHER_TYPES:
self.assertRaises(SystemError, frozendict_asdict, test_type())
self.assertRaises(SystemError, frozendict_asdict, NULL)


if __name__ == "__main__":
unittest.main()
13 changes: 13 additions & 0 deletions Lib/test/test_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -1848,11 +1848,19 @@ def test_merge(self):
frozendict({'x': 1, 'y': 2}))
self.assertEqual(frozendict(x=1, y=2) | frozendict(y=5),
frozendict({'x': 1, 'y': 5}))
self.assertEqual(FrozenDict(x=1, y=2) | FrozenDict(y=5),
frozendict({'x': 1, 'y': 5}))

fd = frozendict(x=1, y=2)
self.assertIs(fd | frozendict(), fd)
self.assertIs(fd | {}, fd)
self.assertIs(frozendict() | fd, fd)

fd = FrozenDict(x=1, y=2)
self.assertEqual(fd | frozendict(), fd)
self.assertEqual(fd | {}, fd)
self.assertEqual(frozendict() | fd, fd)

def test_update(self):
# test "a |= b" operator
d = frozendict(x=1)
Expand All @@ -1863,6 +1871,11 @@ def test_update(self):
self.assertEqual(d, frozendict({'x': 1, 'y': 2}))
self.assertEqual(copy, frozendict({'x': 1}))

def test_items_xor(self):
# test "a ^ b" operator on items views
res = frozendict(a=1, b=2).items() ^ frozendict(b=2, c=3).items()
self.assertEqual(res, {('a', 1), ('c', 3)})

def test_repr(self):
d = frozendict()
self.assertEqual(repr(d), "frozendict()")
Expand Down
9 changes: 9 additions & 0 deletions Modules/_testcapi/dict.c
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,14 @@ frozendict_new(PyObject *self, PyObject *obj)
}


static PyObject*
frozendict_asdict(PyObject *self, PyObject *obj)
{
NULLABLE(obj);
return PyFrozenDict_AsDict(obj);
}


static PyMethodDef test_methods[] = {
{"dict_containsstring", dict_containsstring, METH_VARARGS},
{"dict_getitemref", dict_getitemref, METH_VARARGS},
Expand All @@ -311,6 +319,7 @@ static PyMethodDef test_methods[] = {
{"anydict_check", anydict_check, METH_O},
{"anydict_checkexact", anydict_checkexact, METH_O},
{"frozendict_new", frozendict_new, METH_O},
{"frozendict_asdict", frozendict_asdict, METH_O},
{NULL},
};

Expand Down
20 changes: 19 additions & 1 deletion Objects/clinic/dictobject.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

63 changes: 43 additions & 20 deletions Objects/dictobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,9 @@ static int dict_merge_from_seq2(PyObject *d, PyObject *seq2, int override);

/*[clinic input]
class dict "PyDictObject *" "&PyDict_Type"
class frozendict "PyFrozenDictObject *" "&PyFrozenDict_Type"
[clinic start generated code]*/
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=f157a5a0ce9589d6]*/
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=5dfa93bac68e7c54]*/


/*
Expand Down Expand Up @@ -4384,7 +4385,23 @@ copy_lock_held(PyObject *o, int as_frozendict)
return NULL;
}

// Similar to PyDict_Copy(), but copy also frozendict.
PyObject *
PyDict_Copy(PyObject *o)
{
if (o == NULL || !PyDict_Check(o)) {
PyErr_BadInternalCall();
return NULL;
}

PyObject *res;
Py_BEGIN_CRITICAL_SECTION(o);
res = copy_lock_held(o, 0);
Py_END_CRITICAL_SECTION();
return res;
}

// Similar to PyDict_Copy(), but return a frozendict if the argument
// is a frozendict.
static PyObject *
_PyDict_Copy(PyObject *o)
{
Expand All @@ -4397,27 +4414,14 @@ _PyDict_Copy(PyObject *o)
return res;
}

PyObject *
PyDict_Copy(PyObject *o)
PyObject*
PyFrozenDict_AsDict(PyObject *o)
{
if (o == NULL || !PyAnyDict_Check(o)) {
if (o == NULL || !PyFrozenDict_Check(o)) {
PyErr_BadInternalCall();
return NULL;
}

if (PyFrozenDict_CheckExact(o)) {
return Py_NewRef(o);
}

return _PyDict_Copy(o);
}

// Similar to PyDict_Copy(), but return a dict if the argument is a frozendict.
PyObject*
_PyDict_CopyAsDict(PyObject *o)
{
assert(PyAnyDict_Check(o));

PyObject *res;
Py_BEGIN_CRITICAL_SECTION(o);
res = copy_lock_held(o, 0);
Expand Down Expand Up @@ -6523,7 +6527,7 @@ dictitems_xor_lock_held(PyObject *d1, PyObject *d2)
ASSERT_DICT_LOCKED(d1);
ASSERT_DICT_LOCKED(d2);

PyObject *temp_dict = copy_lock_held(d1, PyFrozenDict_Check(d1));
PyObject *temp_dict = copy_lock_held(d1, 0);
if (temp_dict == NULL) {
return NULL;
}
Expand Down Expand Up @@ -8057,7 +8061,7 @@ static PyMethodDef frozendict_methods[] = {
DICT_ITEMS_METHODDEF
DICT_VALUES_METHODDEF
DICT_FROMKEYS_METHODDEF
DICT_COPY_METHODDEF
FROZENDICT_COPY_METHODDEF
DICT___REVERSED___METHODDEF
{"__class_getitem__", Py_GenericAlias, METH_O|METH_CLASS, PyDoc_STR("See PEP 585")},
{"__getnewargs__", frozendict_getnewargs, METH_NOARGS},
Expand Down Expand Up @@ -8182,6 +8186,25 @@ PyFrozenDict_New(PyObject *iterable)
}
}

/*[clinic input]
frozendict.copy

Return a shallow copy of the frozendict.
[clinic start generated code]*/

static PyObject *
frozendict_copy_impl(PyFrozenDictObject *self)
/*[clinic end generated code: output=e580fd91d9fc2cf7 input=35f6abeaa08fd4bc]*/
{
assert(PyFrozenDict_Check(self));

if (PyFrozenDict_CheckExact(self)) {
return Py_NewRef(self);
}

return _PyDict_Copy((PyObject*)self);
}


PyTypeObject PyFrozenDict_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
Expand Down
8 changes: 7 additions & 1 deletion Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -4850,7 +4850,13 @@ type_new_get_slots(type_new_ctx *ctx, PyObject *dict)
static PyTypeObject*
type_new_init(type_new_ctx *ctx)
{
PyObject *dict = _PyDict_CopyAsDict(ctx->orig_dict);
PyObject *dict;
if (PyFrozenDict_Check(ctx->orig_dict)) {
dict = PyFrozenDict_AsDict(ctx->orig_dict);
}
else {
dict = PyDict_Copy(ctx->orig_dict);
}
if (dict == NULL) {
goto error;
}
Expand Down
Loading