10

Couldn't find a unified resource that documents/explains this. I'm puzzled; is it an operator or not? Most importantly, what is its precedence? An example:

import functools
def array_sum(array):
    return functools.reduce(lambda acc, curr: acc + curr, array)
print(array_sum([1,2])) # 3
# https://docs.python.org/3/reference/expressions.html#operator-precedence tells us that lambda has the lowest precedence.
# But the above code works, which means the comma gets applied after the lambda expression.
# If the comma was applied first, that would give us this runtime error: TypeError: reduce expected at least 2 arguments, got 1

Note that the comma isn't even listed as an operator in the link in the code comments. But here it is called an operator: https://docs.python.org/3/reference/expressions.html#parenthesized-forms

Python version: 3.7.4


EDIT:

I had originally provided this as an additional but incorrect example:

x = 4 if False else 3, 2
print(x) # (3,2)
x = 4 if True else 3, 2
print(x) # 4
# comma was applied before assignment operator

x, y = 4 if True else 3, 2
print(x) # 4
print(y) # 2
# comma was applied after assignment operator

But that was my mistake, because

x = 4 if True else 3, 2
print(x) # 4

was incorrect, and after running it again I saw that when corrected it is:

x = 4 if True else 3, 2
print(x) # (4, 2)

Therefore the comma precedence is behaving consistently in the example that was removed in the edit.

6
  • 2
    A comma is not an operator in Python, but it is an important character several different types of expressions/statements. But interesting that it is referred to in that way in the docs... I don't know if that is correct or not. Commented Sep 10, 2019 at 20:09
  • comma yield tuples, think about parenthesis being optional Commented Sep 10, 2019 at 20:13
  • @juanpa.arrivillaga The second link to the official docs calls it an operator... Are you saying that is incorrect, then? Commented Sep 10, 2019 at 20:13
  • 1
    I'd love it if someone could link to the docs explaining why x = 4 if True else 3, 2 and x, y = 4 if True else 3, 2 act differently. I would have assumed the latter would have given an error because the RHS would evaluate to a single, non-iterable value Commented Sep 10, 2019 at 20:14
  • 2
    The comma is used both as a tuple constructor and a syntactic element in things like function argument lists, import lists, and subscript lists. Its behavior depends on context. Commented Sep 10, 2019 at 20:23

3 Answers 3

5

The right hand side:

4 if False else 3, 2

is evaluated first, and can be visualized as ((4 if False else 3), 2), which is an implicit tuple (a tuple without (), but a tuple nonetheless), and will eventually be 3, 2 or 4, 2 (depending on the evaluation of the if condition), then, the assignments will be made depending on what's on the left hand side, so, since there's only one variable on the left hand side in x = 4 if False else 3, 2, x will be a tuple ((3, 2) or (4, 2)), but if you use unpacking, as in x, y = 4 if False else 3, 2, the assignment will be:

x := 4 if False else 3
y := 2
Sign up to request clarification or add additional context in comments.

8 Comments

Image
What do you mean by "implicit tuple" as opposed to an... explicit one? The parentheses do not make the tuple, they are only required in certain cases where it would be ambiguous.
@lucidbrot Basically, if you're assigning to a tuple, it parses the RHS as a tuple, rather than a single expression whose value might be a tuple.
@MrGeek He understands how it's being interpreted, the question is why.
It might be better to write the "interpreted as" as x, y = (4 if False else 3), 2
@MrGeek That's a consequence of the assignment statement, not the parentheses. x = 1,2 x = (1, 2) are equivalent, as are x, y = (1,2) and x, y = 1, 2.
|
3

The documentation of Assignment Statements makes a distinction between assigning to a single target or multiple targets.

If the target list is a single target with no trailing comma, optionally in parentheses, the object is assigned to that target.

So in the first case, the RHS of the assignment (called the "object" in the documentation) is parsed as a single object. The comma is parsed as the value of the else clause of the conditional expression.

Else: The object must be an iterable with the same number of items as there are targets in the target list, and the items are assigned, from left to right, to the corresponding targets.

So in this case it first tries to parse the RHS as an iterable expression with two items. The comma is treated as the delimiter between the items in a generator expression.

Comments

2

The RHS is parsed as a tuple in both cases, is possible to see this by printing the AST generated.

The difference is that in second case unpacking happens while in the first it doesn't

>>> from astpretty import pprint as pp
>>> import ast
>>> a = ast.parse('x = 1 if True else 2, 3')
>>> pp(a)
Module(
    body=[
        Assign(
            lineno=1,
            col_offset=0,
            targets=[Name(lineno=1, col_offset=0, id='x', ctx=Store())],
            value=Tuple(
                lineno=1,
                col_offset=4,
                elts=[
                    IfExp(
                        lineno=1,
                        col_offset=4,
                        test=NameConstant(lineno=1, col_offset=9, value=True),
                        body=Num(lineno=1, col_offset=4, n=1),
                        orelse=Num(lineno=1, col_offset=19, n=2),
                    ),
                    Num(lineno=1, col_offset=22, n=3),
                ],
                ctx=Load(),
            ),
        ),
    ],
)
>>> b = ast.parse('x, y = 4 if True else 3, 2')
>>> pp(b)
Module(
    body=[
        Assign(
            lineno=1,
            col_offset=0,
            targets=[
                Tuple(
                    lineno=1,
                    col_offset=0,
                    elts=[
                        Name(lineno=1, col_offset=0, id='x', ctx=Store()),
                        Name(lineno=1, col_offset=3, id='y', ctx=Store()),
                    ],
                    ctx=Store(),
                ),
            ],
            value=Tuple(
                lineno=1,
                col_offset=7,
                elts=[
                    IfExp(
                        lineno=1,
                        col_offset=7,
                        test=NameConstant(lineno=1, col_offset=12, value=True),
                        body=Num(lineno=1, col_offset=7, n=4),
                        orelse=Num(lineno=1, col_offset=22, n=3),
                    ),
                    Num(lineno=1, col_offset=25, n=2),
                ],
                ctx=Load(),
            ),
        ),
    ],
)

The documentation that cover this is a little hard to track. First the assignment expression is found here https://docs.python.org/3.7/reference/simple_stmts.html#assignment-statements

The RHS part is a starred_expression which leads to https://docs.python.org/3.7/reference/expressions.html#expression-lists

Except when part of a list or set display, an expression list containing at least one comma yields a tuple. The length of the tuple is the number of expressions in the list. The expressions are evaluated from left to right.

Take a note here:

containing at least one comma yields a tuple

So here is your answer. Anything in RHS with a comma, that is not a list or a set, yields a tuple

I hope that helps

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.