]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
bpo-46903: Handle str-subclasses in virtual instance dictionaries. (GH-31658)
authorMark Shannon <mark@hotpy.org>
Fri, 4 Mar 2022 11:31:29 +0000 (11:31 +0000)
committerGitHub <noreply@github.com>
Fri, 4 Mar 2022 11:31:29 +0000 (11:31 +0000)
Include/internal/pycore_code.h
Lib/test/test_unicode.py
Misc/NEWS.d/next/Core and Builtins/2022-03-03-12-02-41.bpo-46903.OzgaFZ.rst [new file with mode: 0644]
Objects/dictobject.c
Python/specialize.c

index 25c31a1fca7a628db8dba724908f532c0d6741c6..2e03358dfcd47863cfb328a58374df31d7777e86 100644 (file)
@@ -398,6 +398,7 @@ typedef struct _object_stats {
     uint64_t dict_materialized_on_request;
     uint64_t dict_materialized_new_key;
     uint64_t dict_materialized_too_big;
+    uint64_t dict_materialized_str_subclass;
 } ObjectStats;
 
 typedef struct _stats {
index 8e4e64808b6882c4e7b356267364befdcd848332..e97f971b77a63dc38422cccfcf2a748bd142a4ae 100644 (file)
@@ -3044,6 +3044,30 @@ class StringModuleTest(unittest.TestCase):
             ]])
         self.assertRaises(TypeError, _string.formatter_field_name_split, 1)
 
+    def test_str_subclass_attr(self):
+
+        name = StrSubclass("name")
+        name2 = StrSubclass("name2")
+        class Bag:
+            pass
+
+        o = Bag()
+        with self.assertRaises(AttributeError):
+            delattr(o, name)
+        setattr(o, name, 1)
+        self.assertEquals(o.name, 1)
+        o.name = 2
+        self.assertEquals(list(o.__dict__), [name])
+
+        with self.assertRaises(AttributeError):
+            delattr(o, name2)
+        with self.assertRaises(AttributeError):
+            del o.name2
+        setattr(o, name2, 3)
+        self.assertEquals(o.name2, 3)
+        o.name2 = 4
+        self.assertEquals(list(o.__dict__), [name, name2])
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-03-03-12-02-41.bpo-46903.OzgaFZ.rst b/Misc/NEWS.d/next/Core and Builtins/2022-03-03-12-02-41.bpo-46903.OzgaFZ.rst
new file mode 100644 (file)
index 0000000..f6120ef
--- /dev/null
@@ -0,0 +1,2 @@
+Make sure that str subclasses can be used as attribute names for instances
+with virtual dictionaries. Fixes regression in 3.11alpha
index d8bf164f98ee6b60a5b3dea7556be73457ca34d3..635a738985c01d752d4d600c2bee30f1ba14e226 100644 (file)
@@ -5427,23 +5427,26 @@ int
 _PyObject_StoreInstanceAttribute(PyObject *obj, PyDictValues *values,
                               PyObject *name, PyObject *value)
 {
-    assert(PyUnicode_CheckExact(name));
     PyDictKeysObject *keys = CACHED_KEYS(Py_TYPE(obj));
     assert(keys != NULL);
     assert(values != NULL);
     assert(Py_TYPE(obj)->tp_flags & Py_TPFLAGS_MANAGED_DICT);
-    Py_ssize_t ix = insert_into_dictkeys(keys, name);
+    Py_ssize_t ix = DKIX_EMPTY;
+    if (PyUnicode_CheckExact(name)) {
+        ix = insert_into_dictkeys(keys, name);
+    }
     if (ix == DKIX_EMPTY) {
-        if (value == NULL) {
-            PyErr_SetObject(PyExc_AttributeError, name);
-            return -1;
-        }
 #ifdef Py_STATS
-        if (shared_keys_usable_size(keys) == SHARED_KEYS_MAX_SIZE) {
-            OBJECT_STAT_INC(dict_materialized_too_big);
+        if (PyUnicode_CheckExact(name)) {
+            if (shared_keys_usable_size(keys) == SHARED_KEYS_MAX_SIZE) {
+                OBJECT_STAT_INC(dict_materialized_too_big);
+            }
+            else {
+                OBJECT_STAT_INC(dict_materialized_new_key);
+            }
         }
         else {
-            OBJECT_STAT_INC(dict_materialized_new_key);
+            OBJECT_STAT_INC(dict_materialized_str_subclass);
         }
 #endif
         PyObject *dict = make_dict_from_instance_attributes(keys, values);
@@ -5452,7 +5455,12 @@ _PyObject_StoreInstanceAttribute(PyObject *obj, PyDictValues *values,
         }
         *_PyObject_ValuesPointer(obj) = NULL;
         *_PyObject_ManagedDictPointer(obj) = dict;
-        return PyDict_SetItem(dict, name, value);
+        if (value == NULL) {
+            return PyDict_DelItem(dict, name);
+        }
+        else {
+            return PyDict_SetItem(dict, name, value);
+        }
     }
     PyObject *old_value = values->values[ix];
     Py_XINCREF(value);
index 66dce8c93d77b845c347c8a3c935259517975fc9..912b9e29198ee0586fb685872608c58baa0f9419 100644 (file)
@@ -221,6 +221,7 @@ print_object_stats(FILE *out, ObjectStats *stats)
     fprintf(out, "Object materialize dict (on request): %" PRIu64 "\n", stats->dict_materialized_on_request);
     fprintf(out, "Object materialize dict (new key): %" PRIu64 "\n", stats->dict_materialized_new_key);
     fprintf(out, "Object materialize dict (too big): %" PRIu64 "\n", stats->dict_materialized_too_big);
+    fprintf(out, "Object materialize dict (str subclass): %" PRIu64 "\n", stats->dict_materialized_str_subclass);
 }
 
 static void