Multiple object returns from method not acting as expected

Question:

While playing around with pyautogui I decided to make a method to limit the position of my cursor depending on the size of my monitor, since I was moving my cursor randomy.

This is what my first attempt looked like.

max_x = 10
max_y = 40
def limitXandY(x_arg, y_arg):
    # expected to outputs a tuple of integers with length 2
    return x_arg, y_arg if x_arg < max_x and y_arg < max_y else max_x, max_y

When running the code I did not get the expected output.

print(limitXandY(5, 19)) # (5, 19, 40) => (x_arg, y_arg, max_y)
x, y = limitXandY(5, 19) # ValueError: too many values to unpack (expected 2)
print(limitXandY(50, 100)) # (50, 10, 40) => (x_arg, max_x, max_y)
x, y = limitXandY(50, 100) # ValueError: too many values to unpack (expected 2)

As you can see above, my method returns a tuple of length 3 instead of 2. If the ternary operator goes in the if statement, I get the desired output plus last value of the else statement and if the operator returns in the else statement, I get the first value of the if statement as the first value of the methods output tuple.

I have found a work around using parentheses, but this doesn’t really satisfy me and I would like to understand why this ternary operator acts like this.

def correctedLimitXandY(x_arg, y_arg):
    # expected to output a tuple of tuple with length 1
    # inner tuple should containt integers of length 2
    return (x_arg, y_arg) if x_arg < max_x and y_arg < max_y else (max_x, max_y)

print(correctedLimitXandY(5, 19)) # (5, 19) => (x_arg, y_arg)
x, y = correctedLimitXandY(5, 19) # no error
print(correctedLimitXandY(50, 100)) # (10, 40) => (max_x, max_y)
x, y = correctedLimitXandY(50, 100) # no error

Any explanations would be a help!

Asked By: Olivier Neve

||

Answers:

Your first snippet of code is equivalent to this:

return x_arg, (y_arg if x_arg < max_x and y_arg < max_y else max_x), max_y

Which is a tuple of three elements.

This has to do with the fact that the interpreter first reaches the comma than the conditional. That is: it first decides is interpreting a tuple and then finds a conditional expression.

Answered By: Jorge Luis

Any explanations would be a help!

While ternaries have a fairly low precedence, they’re still considered an operator and thus binding "tighter" than tuple literal, much like e.g.

a+b,c+d

is interpreted as

(a + b), (c + d)

not

a + (b, c) + d

When you write

x_arg, y_arg if x_arg < max_x and y_arg < max_y else max_x, max_y

Python will first "resolve" <, then and, then if/else, and only after that does it resolve the tuples, so the result is:

x_arg, (y_arg if ((x_arg < max_x) and (y_arg < max_y)) else max_x), max_y

You can infer this from the full language grammar, specifically:

expressions:
    | expression (',' expression )+ [','] 
    | expression ',' 
    | expression

expression:
    | disjunction 'if' disjunction 'else' expression 
    | disjunction
    | lambdef

Here you can see that an expressions is a comma-separated sequence of expression, and each expression can be a ternary (if/else), therefore the language "resolves" ternaries (the inner structure) first.

Answered By: Masklinn