Welcome to Software Development on Codidact!
Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.
Does Python have a "ternary operator" (conditional evaluation operator like "?:" in other languages)?
Sometimes code needs to assign (or otherwise use) a value that depends on some condition. The naive approach is to use explicit branching, which in Python would look like:
if some_condition():
a_variable = condition_value
else:
a_variable = not_condition_value
However, this seems needlessly verbose, and is not DRY - the assignment syntax is repeated.
Many other popular languages support some way to write an expression that evaluates to either of two stated options, depending on the condition. Typically this uses what is often called a "ternary conditional operator" or just "the ternary operator"[1], commonly written using ?
and :
symbols - so it looks like:
a_variable = some_condition() ? condition_value : not_condition_value
Generally this operator is short-circuiting: i.e., if condition_value
and/or not_condition_value
are more complex expressions, only the necessary one is evaluated according to the some_condition()
result.
Does Python have any syntax like this?
-
Here, "ternary" simply means that there are three operands; but this operator is often referred to as "the" ternary operator because it is typically the only one in the language with that property. ↩︎
2 answers
The Python ternary "operator" is if
and else
:
x = 1 if some_condition else 0
In Python, this is called a "conditional expression" rather than an operator, and the documentation explains it: https://docs.python.org/3.12/reference/expressions.html?highlight=ternary#conditional-expressions
0 comment threads
How to do it
Yes, Python has an equivalent operator. However, it's spelled with keywords rather than punctuation, and it uses a different order of operands. It looks like:
condition_value if some_condition() else not_condition_value
This creates an expression, so the result can then be assigned to a variable etc. It's officially labelled as a "conditional expression" in the Python grammar. Its semantics cannot be changed using operator overloading.
The difference in order is a deliberate part of the design. There was some controversy over it at the time, but it appears to be intended that the reordered expression reads naturally and emphasizes the common case: "the result shall be condition_value
, if some_condition()
is met; else
it will be not_condition_value
.
Like with ?:
in other languages, the operands here will be evaluated at most once, and the overall process will short-circuit. That is, if condition_value
and/or not_condition_value
are instead more complex expressions, only the one that will actually be used gets evaluated.
Conditional assignment
Sometimes people want to use this syntax to make a variable assignment only when a condition is met, and leave the variable alone otherwise.
This can be implemented by just using the existing value for the false condition:
a_variable = condition_value if some_condition() else a_variable
However, this is still not DRY, and it's not very intuitive.
It is generally considered better to just use an explicit if
statement here:
if some_condition():
a_variable = condition_value
There is no real value in complicating it any further.
Just for completeness, it's possible to get something like this in Python 3.8 and onward, by abusing the assignment expression for the true condition and using an ignored dummy value for the false condition:
>>> (a := "it was true") if True else None
'it was true'
>>> a
'it was true'
>>> (a := "it was true") if False else None # evaluates to None
>>> a # still has the old value
'it was true'
This approach could also use an assignment expression could be used for both branches, to assign to different variables. However, this sort of code is confusing and offers no real practical benefit.
Other common attempts will not work.
Since this conditional expression is an expression, its operands must also be valid expressions. It can't use non-expression statements:
>>> pass if True else "it wasn't true"
File "<stdin>", line 1
pass if True else "it wasn't true"
^^
SyntaxError: invalid syntax
Here, the pass
is invalid, as it is a statement.
Like with ?:
in other languages, a result must be provided for both cases:
>>> a = "it was true" if True
File "<stdin>", line 1
a = "it was true" if True
^^^^^^^^^^^^^^^^^^^^^
SyntaxError: expected 'else' after 'if' expression
(Older versions of Python may give less helpful error messages.)
Other approaches
Very old legacy code from before Python 2.5 - or code written by people who learned very old idioms and haven't updated - may try to work around the lack of such syntax support, even though the support is actually there.
and
-or
combination
An old idiom exploits the semantics of and
and or
operators, which evaluate to either of their operands rather than to a boolean value. Thus, one may see:
some_condition() and condition_value or not_condition_value
This is in some sense a natural extension of another old idiom using or
to specify "default" values.
This does short-circuit as desired, and evaluates the operands only once; and parentheses are not needed because the and
takes precedence over or
. However, this breaks when condition_value
is falsy - since some_condition() and condition_value
will then also be falsy, the result is not_condition_value
.
Some code may work around this limitation by wrapping the results in single-element lists (or tuples), so that the values are always truthy, and then extracting what is needed:
# This is safe even if condition_value is False, None, etc.
[some_condition() and [condition_value] or [not_condition_value]][0]
However, this looks awkward, and it's hard to remember the extra steps.
Selection by using the conditional as an index
Another approach is to put the two options for the result into a list (anything that can be indexed by an integer would work), and then use the condition to index into that list. This works by itself when the condition value is a boolean:
# `some_condition()` should return True or False specifically
[not_condition_value, condition_value][some_condition()]
If necessary, the condition value could just be coerced first:
# This way is safer
[not_condition_value, condition_value][bool(some_condition())]
The idea is that False
is equivalent to 0
and True
is equivalent to 1
when used as an index. (Further adjustments would be required to use a dictionary to store the possible results - this is left as an exercise.)
This can sometimes be a natural solution, that can be seen as a special case of a more general "dispatching" approach (where some_condition()
could have multiple relevant values, and is used to select the right result). However, keep in mind that this does not short-circuit.
0 comment threads