diff --git a/Lib/_ast_unparse.py b/Lib/_ast_unparse.py index 916bb25d74dee9b..58bc06397f5134b 100644 --- a/Lib/_ast_unparse.py +++ b/Lib/_ast_unparse.py @@ -925,7 +925,11 @@ def visit_Attribute(self, node): # Special case: 3.__abs__() is a syntax error, so if node.value # is an integer literal then we need to either parenthesize # it or add an extra space to get 3 .__abs__(). - if isinstance(node.value, Constant) and isinstance(node.value.value, int): + # bool is a subclass of int, but True.real and False.real are + # valid without a space. + if (isinstance(node.value, Constant) + and isinstance(node.value.value, int) + and not isinstance(node.value.value, bool)): self.write(" ") self.write(".") self.write(node.attr) diff --git a/Lib/test/test_unparse.py b/Lib/test/test_unparse.py index 9bc04fc8e0affb9..b1f359764fa4fed 100644 --- a/Lib/test/test_unparse.py +++ b/Lib/test/test_unparse.py @@ -335,6 +335,14 @@ def test_unary_parens(self): def test_integer_parens(self): self.check_ast_roundtrip("3 .__abs__()") + def test_attribute_on_bool(self): + # gh-158237: True.real should not gain a space + self.check_src_roundtrip("x = True.real") + self.check_src_roundtrip("x = False.__class__") + + def test_attribute_on_int_still_spaced(self): + self.check_src_roundtrip("x = 3 .__abs__()") + def test_huge_float(self): self.check_ast_roundtrip("1e1000") self.check_ast_roundtrip("-1e1000") diff --git a/Misc/NEWS.d/next/Library/2026-09-26-22-07-09.gh-issue-158237.AbCdEf.rst b/Misc/NEWS.d/next/Library/2026-09-26-22-07-09.gh-issue-158237.AbCdEf.rst new file mode 100644 index 000000000000000..1c28e527dea042e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-09-26-22-07-09.gh-issue-158237.AbCdEf.rst @@ -0,0 +1,3 @@ +Fix :func:`ast.unparse` to not insert a space before attribute +access on bool constants (e.g. ``True.real`` instead of +``True .real``).