pythonmethodsconditional-operatorpyautoguimultiple-return-values

Multiple object returns from method not acting as expected


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!


Solution

  • 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.