]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-143469: enable `LOAD_ATTR_MODULE` specialization even if `__getattr__` is defined...
authorKumar Aditya <kumaraditya@python.org>
Tue, 6 Jan 2026 16:39:18 +0000 (22:09 +0530)
committerGitHub <noreply@github.com>
Tue, 6 Jan 2026 16:39:18 +0000 (22:09 +0530)
Lib/test/test_opcache.py
Misc/NEWS.d/next/Core_and_Builtins/2026-01-06-12-30-03.gh-issue-143469.vHVhEY.rst [new file with mode: 0644]
Python/specialize.c

index be9b52b8bc45e200d7dc1a7ed9f75416df63b5e6..0344f08faecce79cdd987c48f7a35008b01e6420 100644 (file)
@@ -1909,6 +1909,44 @@ class TestSpecializer(TestBase):
         self.assert_no_opcode(my_list_append, "CALL_LIST_APPEND")
         self.assert_no_opcode(my_list_append, "CALL")
 
+    @cpython_only
+    @requires_specialization_ft
+    def test_load_attr_module_with_getattr(self):
+        module = types.ModuleType("test_module_with_getattr")
+        module.__dict__["some_attr"] = "foo"
+
+        def module_getattr(name):
+            if name == "missing_attr":
+                return 42
+            raise AttributeError(f"module has no attribute {name}")
+
+        module.__dict__["__getattr__"] = module_getattr
+
+        import sys
+        sys.modules.pop("test_module_with_getattr", None)
+        sys.modules["test_module_with_getattr"] = module
+        try:
+            def load_module_attr_present():
+                import test_module_with_getattr
+                return test_module_with_getattr.some_attr
+
+            for _ in range(_testinternalcapi.SPECIALIZATION_THRESHOLD):
+                self.assertEqual(load_module_attr_present(), "foo")
+
+            self.assert_specialized(load_module_attr_present, "LOAD_ATTR_MODULE")
+            self.assert_no_opcode(load_module_attr_present, "LOAD_ATTR")
+
+            def load_module_attr_missing():
+                import test_module_with_getattr
+                return test_module_with_getattr.missing_attr
+
+            for _ in range(_testinternalcapi.SPECIALIZATION_THRESHOLD):
+                self.assertEqual(load_module_attr_missing(), 42)
+
+            self.assert_no_opcode(load_module_attr_missing, "LOAD_ATTR_MODULE")
+        finally:
+            sys.modules.pop("test_module_with_getattr", None)
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-01-06-12-30-03.gh-issue-143469.vHVhEY.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-01-06-12-30-03.gh-issue-143469.vHVhEY.rst
new file mode 100644 (file)
index 0000000..9bac544
--- /dev/null
@@ -0,0 +1 @@
+Enable :opcode:`!LOAD_ATTR_MODULE` specialization even if :func:`!__getattr__` is defined in module.
index 62f0373a4c274d609966f2dc35bce87a75b35964..b7fa5f8ad4c04e6713ab952dd2523d526042de56 100644 (file)
@@ -366,14 +366,8 @@ specialize_module_load_attr_lock_held(PyDictObject *dict, _Py_CODEUNIT *instr, P
         SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_ATTR_NON_STRING);
         return -1;
     }
-    Py_ssize_t index = _PyDict_LookupIndex(dict, &_Py_ID(__getattr__));
+    Py_ssize_t index = _PyDict_LookupIndex(dict, name);
     assert(index != DKIX_ERROR);
-    if (index != DKIX_EMPTY) {
-        SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_ATTR_MODULE_ATTR_NOT_FOUND);
-        return -1;
-    }
-    index = _PyDict_LookupIndex(dict, name);
-    assert (index != DKIX_ERROR);
     if (index != (uint16_t)index) {
         SPECIALIZATION_FAIL(LOAD_ATTR,
                             index == DKIX_EMPTY ?