https://github.com/python/cpython/commit/6979003a1d73383ce798eb1301fe93d4d74b1903
commit: 6979003a1d73383ce798eb1301fe93d4d74b1903
branch: main
author: tonghuaroot (童话) <[email protected]>
committer: JelleZijlstra <[email protected]>
date: 2026-10-12T23:37:58Z
summary:

gh-152692: Fix annotationlib.type_repr() for bound built-in methods (#152693)

type_repr() returned a string naming a nonexistent module None -- e.g.
type_repr([].append) == 'None.list.append' -- for bound built-in methods,
whose __module__ is None.

Return the qualified name when __module__ is None, as already done for the
"builtins" module.

files:
A Misc/NEWS.d/next/Library/2026-06-30-15-00-00.gh-issue-152692.tr3pr0.rst
M Lib/annotationlib.py
M Lib/test/test_annotationlib.py

diff --git a/Lib/annotationlib.py b/Lib/annotationlib.py
index 26121e3af31cb9..c41df197eee7d5 100644
--- a/Lib/annotationlib.py
+++ b/Lib/annotationlib.py
@@ -1105,9 +1105,17 @@ def type_repr(value):
 
     """
     if isinstance(value, (type, types.FunctionType, 
types.BuiltinFunctionType)):
-        if value.__module__ == "builtins":
+        module = value.__module__
+        if module is None:
+            # Built-in methods have __module__ set to None; recover the module
+            # from the bound object (the class itself for class-bound methods).
+            obj = getattr(value, "__self__", None)
+            if obj is not None:
+                cls = obj if isinstance(obj, type) else type(obj)
+                module = cls.__module__
+        if module is None or module == "builtins":
             return value.__qualname__
-        return f"{value.__module__}.{value.__qualname__}"
+        return f"{module}.{value.__qualname__}"
     elif isinstance(value, _Template):
         tree = _template_to_ast(value)
         return ast.unparse(tree)
diff --git a/Lib/test/test_annotationlib.py b/Lib/test/test_annotationlib.py
index b5388f3f30aee0..56e5731849cbd7 100644
--- a/Lib/test/test_annotationlib.py
+++ b/Lib/test/test_annotationlib.py
@@ -1888,6 +1888,18 @@ def nested():
             type_repr(nested), 
f"{__name__}.TestTypeRepr.test_type_repr.<locals>.nested"
         )
         self.assertEqual(type_repr(len), "len")
+        # gh-152692: built-in methods have __module__ set to None
+        self.assertEqual(type_repr([].append), "list.append")
+        self.assertEqual(type_repr((1).bit_length), "int.bit_length")
+        self.assertEqual(type_repr(dict.fromkeys), "dict.fromkeys")
+        # Methods of extension types also have __module__ set to None, but
+        # are not in the builtins module.
+        import datetime
+        self.assertEqual(
+            type_repr(datetime.datetime(2000, 1, 1).isoformat),
+            "datetime.datetime.isoformat",
+        )
+        self.assertEqual(type_repr(datetime.date.today), "datetime.date.today")
         self.assertEqual(type_repr(type_repr), "annotationlib.type_repr")
         self.assertEqual(type_repr(times_three), f"{__name__}.times_three")
         self.assertEqual(type_repr(...), "...")
diff --git 
a/Misc/NEWS.d/next/Library/2026-06-30-15-00-00.gh-issue-152692.tr3pr0.rst 
b/Misc/NEWS.d/next/Library/2026-06-30-15-00-00.gh-issue-152692.tr3pr0.rst
new file mode 100644
index 00000000000000..d92011f2c5acd7
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-06-30-15-00-00.gh-issue-152692.tr3pr0.rst
@@ -0,0 +1,4 @@
+Fix :func:`annotationlib.type_repr` returning a string such as
+``None.list.append`` for bound built-in methods, whose ``__module__`` is
+``None``.  It now returns the qualified name, as it already did for other
+builtins.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to