Skip to content

Commit 0c42466

Browse files
[3.14] gh-152692: Fix annotationlib.type_repr() for bound built-in methods (GH-152693) (#159281)
gh-152692: Fix annotationlib.type_repr() for bound built-in methods (GH-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. (cherry picked from commit 6979003) Co-authored-by: tonghuaroot (童话) <tonghuaroot@gmail.com>
1 parent 3fa2a37 commit 0c42466

3 files changed

Lines changed: 26 additions & 2 deletions

File tree

‎Lib/annotationlib.py‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1113,9 +1113,17 @@ def type_repr(value):
11131113
11141114
"""
11151115
if isinstance(value, (type, types.FunctionType, types.BuiltinFunctionType)):
1116-
if value.__module__ == "builtins":
1116+
module = value.__module__
1117+
if module is None:
1118+
# Built-in methods have __module__ set to None; recover the module
1119+
# from the bound object (the class itself for class-bound methods).
1120+
obj = getattr(value, "__self__", None)
1121+
if obj is not None:
1122+
cls = obj if isinstance(obj, type) else type(obj)
1123+
module = cls.__module__
1124+
if module is None or module == "builtins":
11171125
return value.__qualname__
1118-
return f"{value.__module__}.{value.__qualname__}"
1126+
return f"{module}.{value.__qualname__}"
11191127
elif isinstance(value, _Template):
11201128
tree = _template_to_ast(value)
11211129
return ast.unparse(tree)

‎Lib/test/test_annotationlib.py‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1857,6 +1857,18 @@ def nested():
18571857
type_repr(nested), f"{__name__}.TestTypeRepr.test_type_repr.<locals>.nested"
18581858
)
18591859
self.assertEqual(type_repr(len), "len")
1860+
# gh-152692: built-in methods have __module__ set to None
1861+
self.assertEqual(type_repr([].append), "list.append")
1862+
self.assertEqual(type_repr((1).bit_length), "int.bit_length")
1863+
self.assertEqual(type_repr(dict.fromkeys), "dict.fromkeys")
1864+
# Methods of extension types also have __module__ set to None, but
1865+
# are not in the builtins module.
1866+
import datetime
1867+
self.assertEqual(
1868+
type_repr(datetime.datetime(2000, 1, 1).isoformat),
1869+
"datetime.datetime.isoformat",
1870+
)
1871+
self.assertEqual(type_repr(datetime.date.today), "datetime.date.today")
18601872
self.assertEqual(type_repr(type_repr), "annotationlib.type_repr")
18611873
self.assertEqual(type_repr(times_three), f"{__name__}.times_three")
18621874
self.assertEqual(type_repr(...), "...")
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix :func:`annotationlib.type_repr` returning a string such as
2+
``None.list.append`` for bound built-in methods, whose ``__module__`` is
3+
``None``. It now returns the qualified name, as it already did for other
4+
builtins.

0 commit comments

Comments
 (0)