A more elegant "ternary" as function ternary

Hello

Python has a syntax to mimic ternary operator, but its syntax is like inverted as the if result is written before the if. Writing if else for a ternary operator makes other languages to look smarter, as they have a ? and : syntax instead. My proposal is to have a python function called ternary.

# current way Python does ternary condition:
myvar = 7 if a == 0 else 8
# Do you think it is a bit ugly syntax?

My proposal - which I am already using with no issues, is Python to have a ternary function

def ternary( condition, value_if_true, value_if_false, on_exception=None):
try:
if condition:
return value_if_true
else:
return value_if_false
except:
return on_exception

What is the advantage? For me this syntax is cleaner:

# ternary would be a built-in python function, not user defined

myvar = ternary(a == 0, 7, 8)

Specially if you need to nest more than one ternary, the current way gets a nightmare, and this function keeps the code clear.

PS: This is suitable only to use discrete values as options of return: number, string, boolean, list, not “formulas”

Regards

Your version doesn’t short circuit the evaluation of whichever value doesn’t get used (value_if_true and value_if_false are both evaluated). So you couldn’t do something like ternary(a is None, None, a.foo). So it’s not a replacement.

If you find your code useful, you can put it in your own library. But it won’t be added to Python.

8 Likes

Try this:

x = None # or x = 5
print("None" if x is None else x + 2)

Now try that with your function.

2 Likes

You are right, it has this 0.000001% case limitation. For the other 99.9999% cases I will use it.
Thanks for pointing the limitation.

You are correct, it has this 0.001% case limitation. For the other 99.999% cases I will use it.
Thanks for pointing the limitation.

Having a ternary that doesn’t short-circuit sounds like a footgun

4 Likes

At least you have conceded that your first estimate was wrong by a factor of one Thousand. But, you are still wrong by much more than another factor of one Thousand.

(Edit:) But, obviously, you know how important short-circuiting can be, since you have added a try-except-block to your proposal after the fact. However, that change does not fix the issues that have been brought up. The code ternary(a is None, None, a.foo) would still throw an exception.

1 Like

One which, to my knowledge, can never actually catch anything. OP, please don’t make substantive edits like this; an unacknowledged change to the essence of your post makes it difficult to reply usefully, and also difficult to follow the thread afterwards (you can’t know which version of the proposal someone was responding to). Instead, post a followup to suggest the alternative, so that it is itself a post that can easily be replied to.

3 Likes

if condition calls type(condition).__bool__ if condition isn’t already a bool. That function could throw.

I actually quite like the resulting call, personally:

myvar = ternary(a == 0, 7, 8)

But the implementation above is a long way from being ready from builtins

Why on earth should a ternary function or operator, suppress all Exceptions and return a custom on_exception arg (or None)? Instead of either just letting it fail, or logging the exception and reraising it (e.g. except Exception as e: logger.error(e); raise e)? This isn’t close to a proper Maybe Monad pattern (it’s not how Go handles errors is it)?

The stated goal is to replace an if block or an existing Python ‘ternary’, with myvar = ternary(a == 0, 7, 8). But to use this safely, or with normal Exception propagation, I’ve got to wrap it in its own entire extra if block to test if it returns on_exception or not. If I’ve got to always use the same common pattern to call a helper function, I would argue that boilerplate should live inside the helper function (or at least, another one).

1 Like

Ah right, forgot about that. So I guess that makes the OP’s updated function an example, not of a termary function, but of ternary logic: “here is a value, if it’s truthy return this, if it’s falsy return that, if it isn’t boolable return the third”.

What’s true of waterfowl is true of functions like this: One good tern deserves another.

OP, you’re editing the post again instead of posting followups. Please stop. Reply to the thread if you want to say something different.

1 Like

For a Python version of the traditional ? : ternary, how about:

>>> x=True
>>> x and 'a' or 'b'
'a'
>>> x = False
>>> x and 'a' or 'b'
'b'

Argument order was a large part of the prolonged ternary conditional expression discussion. The C order was favored by many, but Guido picked what he ultimately did. The latter works better than many, including me, expected.

The current conditional expression replaced condition and expression or default, which has a subtle flaw that I no longer remember (and do not wish to). Many uses of the replaced compound expression depended on short-circuiting, so the replacement needed to do the same. The general pattern is expression if computing_expression_will not raise else default, where default is a constant or expression that cannot fail. I suspect that this includes more that 1% of uses.

4 Likes

It’s always a pleasure to learn you core devs are way ahead of me. And in this case not only that, from a look at PEP308 you were so, over 20 years ago.

Anyway, one flaw in and / or is if expression 1 is Falsey, it evaluates to expression 2 regardless of the value of the condition.

1 Like

If condition was truthy but expression was falsely it would go on to evaluate default, which a proper ternary operator would not do (short-circuiting is vital).

So, as a joke, I posted this to use instead:

(condition and [expression] or [default])[0]

But too many people took that to heart, and started using that absurdity in their code. As I recall, neither Guido nor I thought Python “needed” a ternary operator at the time, but the spread of the idiom was too ugly to bear.

So Guido reluctantly gave in to an unreasoning mob :wink:,

7 Likes

Thank you Tim. ‘not needed’ in the sense that nearly always one of expession1 or expression2 could not be falsey and if both could be, an explicit if-else statement was available. But as I remember, a) a and b or c was mentioned in a FAQ and b) someone reported a negative experience with a hard-to-notice falsey bug. I now freely use what I once thought unbearably ugly. I suggest OP and others also try to adjust.

3 Likes

Having used it for many years, I now find val1 if cond else val2 to be minorly ugly, but not unbearably so. The fact that it’s evaluated from the middle out, instead of left to right as with C’s style, is a downside. Not a deal-breaker but it does force a re-reading in complex expressions (imagine if the condition is seldom true, and you see that the beginning of an expression will bomb - you have to go further than it to find that there’s actually a conditional check on it).

2 Likes

Your implementation

def ternary( condition, value_if_true, value_if_false, on_exception=None):
     try:
         if condition:
             return value_if_true
         else:
             return value_if_false
     except:
         return on_exception

assumes that the only bad thing that can happen when both value_if_true and value_if_false are evaluated, is an exception. But this is not true, short circuiting is essential:

def backup(filename):
	... Make a backup of the file
	return f"{filename} backed up"

def delete(filename)
	... Delete the file
	return f"{filename} deleted"

def is_garbage(filename):
	... Return whether the filename is garbage (and should be deleted) or not (and should be backed up)

print(delete(filename) if is_garbage(filename) else backup(filename))

Replace the last line with ternary():

print(ternary(is_garbage(filename), delete(filename), backup(filename)))

and suddenly files get deleted that shouldn’t be.

2 Likes