(Reposting this because I replied to the wrong thread.)
The docs also don’t describe the behaviour when explicit grouping is used, which turns out to be a bit weird. The following two functions produce identical bytecode:
def f1(): return (a and b) and c
def f2(): return a and (b and c)
Both of these are compiled as:
1 0 LOAD_GLOBAL 0 (a)
2 JUMP_IF_FALSE_OR_POP 10
4 LOAD_GLOBAL 1 (b)
6 JUMP_IF_FALSE_OR_POP 10
8 LOAD_GLOBAL 2 (c)
>> 10 RETURN_VALUE
This is despite the fact that the ASTs are different:
>>> ast.dump(ast.parse("(a and b) and c"))
"Module(body=[Expr(value=BoolOp(op=And(), values=[BoolOp(op=And(), values=[Name(id='a', ctx=Load()), Name(id='b', ctx=Load())]), Name(id='c', ctx=Load())]))], type_ignores=[])"
>>> ast.dump(ast.parse("a and (b and c)"))
"Module(body=[Expr(value=BoolOp(op=And(), values=[Name(id='a', ctx=Load()), BoolOp(op=And(), values=[Name(id='b', ctx=Load()), Name(id='c', ctx=Load())])]))], type_ignores=[])"
So the and operator (and presumably or too) actually has the same kind of parentheses-escaping behaviour that we’re arguing against with ?.. It’s just that nobody has noticed before, probably because it’s rare for a bool method to have a side effect.