It seems this question only answered for Java but I would like to know how it works in Python. So are these the same?
a += b / 2
and
a += (b / 2)
Yes, those are the same. Python's augmented assignment is not an expression, it is a statement, and doesn't play in expression precedence rules. +=
is not an operator, and instead it's part of the augmented assignment statement syntax.
So everything to the right of the +=
is an expression, but +=
itself is not, so the assignment will always be handled last.
And because (augmented) assignment is not an expression, it can't produce a value to use in a surrounding expression either. There is no (a += b) / 2
, that'd be a syntax error, and certainly no if (a += b / 2):
or other such shenanigans.
See the reference documentation on Augmented assignment statements, which states the grammar is:
augmented_assignment_stmt ::= augtarget augop (expression_list | yield_expression) augtarget ::= identifier | attributeref | subscription | slicing augop ::= "+=" | "-=" | "*=" | "@=" | "/=" | "//=" | "%=" | "**=" | ">>=" | "<<=" | "&=" | "^=" | "|="
So the augop
is part of the statement syntax, and only the part following is an expression (specifically, either a expression_list
or yield_expression
grammar rule).
Furthermore, the explanation shows:
An augmented assignment evaluates the target (which, unlike normal assignment statements, cannot be an unpacking) and the expression list, performs the binary operation specific to the type of assignment on the two operands, and assigns the result to the original target. The target is only evaluated once.
So the augtarget
part is handled first, the expression list (or yield expression) is handled second, and then the augmented assignment applies the operator and assigns back the result.
Furthermore, the expressions reference documentation does include a precedence table, but that table doesn't include assignments (augmented or otherwise), simply because assignments are not expressions but statements.
Short answer: +=
is an augmented assignment, and if we take the grammar into account, this is parsed higher in the syntax tree, than the operators in general (and hence the /
operator in particular).
Python sees the +=
as an "augmented assignment". If we inspect the Python grammar we see:
augassign: ('+=' | '-=' | '*=' | '@=' | '/=' | '%=' | '&=' | '|=' | '^=' | '<<=' | '>>=' | '**=' | '//=')
Now the grammar also enforces the priority rules when parsing. If we look at the grammar that is related to stmt
("statement"), we see:
stmt: simple_stmt | compound_stmt simple_stmt: small_stmt (';' small_stmt)* [';'] NEWLINE small_stmt: (expr_stmt | del_stmt | pass_stmt | flow_stmt | import_stmt | global_stmt | nonlocal_stmt | assert_stmt) expr_stmt: testlist_star_expr (annassign | augassign (yield_expr|testlist) | ('=' (yield_expr|testlist_star_expr))*)
Exhaustively explaining all the other statements (like the del_statement
) would take too long, but the expr_stmt
is the only one that leads to an augassign
(and augassign
is the only variable that results in a +=
token). So we can ignore the other expressions.
Now if we "specialize" the expression of expr_stmt
such that it has an augassign
in it, we retrieve the production rule:
expr_stmt: testlist_star_expr augassign (yield_expr|testlist)
The testlist_star_expr
is a variable that results in an identifier (or multiple identifiers in case of sequence unpacking), etc.
On the right we see a yield_expr
, or a test_list
. A test_list
can result in comma separated expressions, with:
testlist: test (',' test)* [',']
This test
allows to write ternary operators, but that is not mandatory:
test: or_test ['if' or_test 'else' test] | lambdef
We can take the or_test
variable, which is used to group expressions with an or
separator (again optional), since the or
has the highest precedence.
or_test: and_test ('or' and_test)*
Then follows the and_test
which, as the name probably suggests, allows us to write and
operators:
and_test: not_test ('and' not_test)*
then follows the not
operator (with the not_test
):
not_test: 'not' not_test | comparison
We can have an arbitrary number of not
s in front, but eventually we will pick the comparison
.
If we look at the production roule for the comparison
, we see:
comparison: expr (comp_op expr)*
This thus allows comparator chaining, like x <= y < z
, next we look at the expr
:
expr: xor_expr ('|' xor_expr)* xor_expr: and_expr ('^' and_expr)* and_expr: shift_expr ('&' shift_expr)* shift_expr: arith_expr (('<<'|'>>') arith_expr)* arith_expr: term (('+'|'-') term)* term: factor (('*'|'@'|'/'|'%'|'//') factor)*
So this defines precedence rules, and we see that |
takes precedence over ^
, that takes precedence over &
, and so on until we see a term
is a sequence of factor
s with operators of '*'
, '@'
, '/'
, '%'
, and //
, so here we finally "consume" our *
. This thus means that the /
is lower in the syntax tree than the +=
node.
Hence the way Python parses this expression is:
a += (b / 2)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With