From 69b8521b6817fa775ef4caa46ef3381796ac77f0 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Sun, 22 Feb 2026 19:07:31 +0000 Subject: [PATCH 1/2] Add `frozendict` support to `str.maketrans()` and `type()` --- Lib/test/test_builtin.py | 6 ++++ Lib/test/test_str.py | 7 +++++ ...-02-22-19-05-03.gh-issue-145118.bU6Sic.rst | 1 + Objects/typeobject.c | 28 +++++++++++++++---- Objects/unicodeobject.c | 2 +- 5 files changed, 38 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-02-22-19-05-03.gh-issue-145118.bU6Sic.rst diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index 7b69374b1868d1..eabfdcd447f2bb 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -2993,6 +2993,12 @@ def test_type_doc(self): A.__doc__ = doc self.assertEqual(A.__doc__, doc) + def test_type_frozendict(self): + A = type('A', (), frozendict({'x': 4, 'y': 2})) + self.assertEqual(A.x, 4) + self.assertEqual(A.y, 2) + self.assertEqual(A.__name__, 'A') + def test_bad_args(self): with self.assertRaises(TypeError): type() diff --git a/Lib/test/test_str.py b/Lib/test/test_str.py index 0a8dddb026f6c8..4f57499af70f4d 100644 --- a/Lib/test/test_str.py +++ b/Lib/test/test_str.py @@ -454,6 +454,13 @@ def test_maketrans_translate(self): self.assertEqual("[a\xe9]".translate(str.maketrans({'a': '<\u20ac>'})), "[<\u20ac>\xe9]") + # with frozendict + tbl = self.type2test.maketrans(frozendict({'s': 'S', 'T': 't'})) + self.assertEqual(tbl, {ord('s'): 'S', ord('T'): 't'}) + self.assertEqual('sTan'.translate(tbl), 'Stan') + tbl = self.type2test.maketrans(frozendict({'a': None, 'b': ''})) + self.checkequalnofix('c', 'abababc', 'translate', tbl) + # invalid Unicode characters invalid_char = 0x10ffff+1 for before in "a\xe9\u20ac\U0010ffff": diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-02-22-19-05-03.gh-issue-145118.bU6Sic.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-02-22-19-05-03.gh-issue-145118.bU6Sic.rst new file mode 100644 index 00000000000000..4e2c0d2565718e --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-02-22-19-05-03.gh-issue-145118.bU6Sic.rst @@ -0,0 +1 @@ +:meth:`str.maketrans` and :func:`type` now accept :class:`frozendict`. diff --git a/Objects/typeobject.c b/Objects/typeobject.c index ad26339c9c34df..54b0514e7bed6b 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -4872,9 +4872,21 @@ type_new_get_slots(type_new_ctx *ctx, PyObject *dict) static PyTypeObject* type_new_init(type_new_ctx *ctx) { - PyObject *dict = PyDict_Copy(ctx->orig_dict); - if (dict == NULL) { - goto error; + PyObject *dict; + if (PyFrozenDict_Check(ctx->orig_dict)) { + dict = PyDict_New(); + if (dict == NULL) { + goto error; + } + if (PyDict_Merge(dict, ctx->orig_dict, 1) < 0) { + goto error; + } + } + else { + dict = PyDict_Copy(ctx->orig_dict); + if (dict == NULL) { + goto error; + } } if (type_new_get_slots(ctx, dict) < 0) { @@ -5037,13 +5049,19 @@ type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds) /* Parse arguments: (name, bases, dict) */ PyObject *name, *bases, *orig_dict; - if (!PyArg_ParseTuple(args, "UO!O!:type.__new__", + if (!PyArg_ParseTuple(args, "UO!O:type.__new__", &name, &PyTuple_Type, &bases, - &PyDict_Type, &orig_dict)) + &orig_dict)) { return NULL; } + if (!PyAnyDict_Check(orig_dict)) { + PyErr_Format(PyExc_TypeError, + "type.__new__() argument 3 must be dict, not %T", + orig_dict); + return NULL; + } type_new_ctx ctx = { .metatype = metatype, diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index fdcbcf51cb62c2..988e5f95573fe1 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -13149,7 +13149,7 @@ unicode_maketrans_impl(PyObject *x, PyObject *y, PyObject *z) const void *data; /* x must be a dict */ - if (!PyDict_CheckExact(x)) { + if (!PyAnyDict_CheckExact(x)) { PyErr_SetString(PyExc_TypeError, "if you give only one argument " "to maketrans it must be a dict"); goto err; From 64a6a1c69415b3e31f9412ad5010219c11989274 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Sun, 22 Feb 2026 22:03:22 +0000 Subject: [PATCH 2/2] Split --- Lib/test/test_str.py | 7 ------- .../2026-02-22-19-05-03.gh-issue-145118.bU6Sic.rst | 2 +- Objects/unicodeobject.c | 2 +- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/Lib/test/test_str.py b/Lib/test/test_str.py index 4f57499af70f4d..0a8dddb026f6c8 100644 --- a/Lib/test/test_str.py +++ b/Lib/test/test_str.py @@ -454,13 +454,6 @@ def test_maketrans_translate(self): self.assertEqual("[a\xe9]".translate(str.maketrans({'a': '<\u20ac>'})), "[<\u20ac>\xe9]") - # with frozendict - tbl = self.type2test.maketrans(frozendict({'s': 'S', 'T': 't'})) - self.assertEqual(tbl, {ord('s'): 'S', ord('T'): 't'}) - self.assertEqual('sTan'.translate(tbl), 'Stan') - tbl = self.type2test.maketrans(frozendict({'a': None, 'b': ''})) - self.checkequalnofix('c', 'abababc', 'translate', tbl) - # invalid Unicode characters invalid_char = 0x10ffff+1 for before in "a\xe9\u20ac\U0010ffff": diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-02-22-19-05-03.gh-issue-145118.bU6Sic.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-02-22-19-05-03.gh-issue-145118.bU6Sic.rst index 4e2c0d2565718e..24507d4a411f85 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2026-02-22-19-05-03.gh-issue-145118.bU6Sic.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-02-22-19-05-03.gh-issue-145118.bU6Sic.rst @@ -1 +1 @@ -:meth:`str.maketrans` and :func:`type` now accept :class:`frozendict`. +:func:`type` now accepts :class:`frozendict` as an argument. diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index 988e5f95573fe1..fdcbcf51cb62c2 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -13149,7 +13149,7 @@ unicode_maketrans_impl(PyObject *x, PyObject *y, PyObject *z) const void *data; /* x must be a dict */ - if (!PyAnyDict_CheckExact(x)) { + if (!PyDict_CheckExact(x)) { PyErr_SetString(PyExc_TypeError, "if you give only one argument " "to maketrans it must be a dict"); goto err;