Conditional Expresion/Statements

Quite often when coding I feel like this would be a very logical way to express things

retries -= 1 if logical_condition
retries = function_call() if logical_condition
return if logical_condition
return None if logical condition

Obviously this can be seen as nothing new given that both can be written as

if logical_condition:
    retries -= 1

if logical_condition:
    retries = function_call()

if logical_condition:
    return

if logical_condition:
    return None

It is probably because the former can be read in a natural manner like: “return if something_is_True”

Is this an avenue which would be interesting exploring?

Almost certainly not. It’s similar to the Perl “conditional modifier” syntax, which was a key factor in Perl’s “there’s more than one way to do it” style. Python consciously adopted a “there should be one obvious way to do it” style (see the Zen of Python for the exact wording), partly in reaction to Perl’s approach.

So your proposal is going against a pretty fundamental design principle of Python. Times have changed, and it’s possible that people might be more receptive to an idea like this now, if there was a compelling enough case for it, but honestly, I doubt it. If you want to pursue the idea, you should probably search the archives both here and on the old mailing list, to understand what specific reasons people gave for rejecting this proposal in the past (it has come up before), so that you can address those points.

But personally, I’m -1 on the proposal. Yes, it would be convenient at times, but I value consistency and regularity more than occasional convenience, as a general principle.

4 Likes

We do already have a terse syntax for these one-liners:

>>> retries = 2
>>> if True: retries -= 1
...
>>> retries
1

Admittedly, most auto-formatters will reformat it…

1 Like

Or ternary expressions:

retries = 2
retries = retries - 1 if logical_condition else retries

The point is not whether it can be a one liner when read left to right. The point would be if reading it from right to left, which (at least to me) seems more natural would make sense, eliminating also the need for the :

Ternary expressions are almost there, the issue at hand there that the “idea” would avoid the execution of the 2nd part (because it doesn’t exist) which is: retries = retries

Exactly this type of redundant behavior (which I typed over and over again) is what led me in the first place to think that a more compact syntax could actually make sense

Well it can be done shorter without the “redundant” retries:

retries = 2
retries -= 1 if logical_condition else 0

You still have something redundant, which doesn’t add to clarity and generates something which is executing if the logical_condition is not met

retries -= 0  # from retries -= 1 if False else 0

See, I understand that things can already be done with a ternary operator, but the idea is to drop the “third” part and avoid a pointless execution in the 1st place.

Whilst making the statement/expression/call-it-x a bit more natural (imho)

retries -= 1 if logical_condition

which should be read as: retries -= 1 is only going to be executed if: if logical_condition evaluates to True

When I started with Python there were no PEPs (the idea still needed a couple of years) and the idea of the Zen of Python didn’t even exist either. That Guido used the principles for the design and development of Python is out of the question.

(That I started so early makes me not a foremost Python expert, because I am not, it is just a historical reference)

I also like very much the “one obvious way” to do things but I think that train is long gone, which at the same type doesn’t mean one has to add all to the language.

The proposal simply extra-develops the ternary operator by letting one drop the “else” part and with it avoiding the execution of the statement/expression bound to the dernary-operator now.

It is nothing different (as expressed in the OP) from writing it the other way round

if logical_condition:
    retries -= 1

It’s about whether the natural language approach and thinking when confronted with

retries -= 1 if logical_condition

makes sense.

1 Like

I believe this is subjective, it adds clarity on what to do when logical_condition isn’t met.

So currently there are two options:

  1. Syntax that achieves exactly semantically and runtime what you want but in two lines rather than one line
  2. Syntax that is very close to what you want and can be done in one line

So you must justify why a third option needs to be created here.

I would look at the old discussions on the walrus operator which introduced an expression syntax to an existing statement syntax, it was not introduced without significant debate and justification, e.g. Mailman 3 PEP 572: Assignment Expressions - Python-Dev - python.org (there were many more threads in python dev and python ideas, this was just the easiest to find)

That’s not what I meant - the Zen (as you know) codified the principles that were already in use. Sorry for assuming you didn’t know the history, I was also around back then, but I tend to assume many of the proposals we see are from newcomers to the language.

As I said, if you want to pursue this, it’s worth a try, but I think you’ll need a better argument than simple convenience.

You’d also need to consider the fact that

retries -= 1 if logical_condition else 2

is currently valid syntax, and both the parser and human readers[1] need to look ahead quite a long way to distinguish the two cases, and that often hurts readability. Particularly if logical_condition is a complex expression (which it typically is, from my experience with Perl, as that’s when you want to push it to the end, to avoid having it “hide” the main point of the construct, which is to decrement retries).

Maybe you should identify some examples, from public code like the stdlib or 3rd party libraries, where this proposal would improve readability. For completeness (and contrast), you could add some examples of where using it would be a bad idea, and therefore get a feel of how often this would be a useful thing to have.


  1. I was going to say “especially non-English speakers”, but I don’t want to make unwarranted assumptions again, and I personally only speak English so my experience is limited to that. ↩︎

When I learned Python, it was a simple but still expressive programming language (comparing with Awk, Perl, Ruby, Tcl). A typical programmer could grasp the basics in half a day and start writing simple scripts. Then finish reading the official tutorial and consider themself a pro. The rest came with experience.

Now there are thousands of online courses and couches. Maybe it’s time to move away from the old principles and add unrestricted complexity to the syntax. Maybe this is what the next generation needs.

1 Like

I dislike this because it makes it difficult to tell at a glance whether a variable is defined in a block of code. Currently, if a line starts with retries =, you know that later in the same block, that variable will definitely exist. With this proposed syntax, you have to scan for whether there’s an if somewhere in the middle of the expression.

7 Likes

I was also around back then

Hong Kong Phooey is for sure testimony to that. I also watched it as a kid.

In any case, you may consider (I do) me a beginner, I have never worked professionally as a programmer.

Back in business.

Maybe you should identify some examples, from public code like the stdlib or 3rd party libraries, where this proposal would improve readability. For completeness (and contrast), you could add some examples of where using it would be a bad idea, and therefore get a feel of how often this would be a useful thing to have.

I had threading.py open and had the __init__ method of Condition at hand.

Original

    def __init__(self, lock=None):
        if lock is None:
            lock = RLock()
        self._lock = lock
        # Export the lock's acquire() and release() methods
        self.acquire = lock.acquire
        self.release = lock.release
        # If the lock defines _release_save() and/or _acquire_restore(),
        # these override the default implementations (which just call
        # release() and acquire() on the lock).  Ditto for _is_owned().
        if hasattr(lock, '_release_save'):
            self._release_save = lock._release_save
        if hasattr(lock, '_acquire_restore'):
            self._acquire_restore = lock._acquire_restore
        if hasattr(lock, '_is_owned'):
            self._is_owned = lock._is_owned
        self._waiters = _deque()

Syntax-modified

    def __init__(self, lock=None):
        self._lock = lock = RLock() if lock is None

        # Export the lock's acquire() and release() methods
        self.acquire = lock.acquire
        self.release = lock.release

        # If the lock defines _release_save() and/or _acquire_restore(),
        # these override the default implementations (which just call
        # release() and acquire() on the lock).  Ditto for _is_owned().
        self._release_save = lock._release_save if hasattr(lock, '_release_save')
        self._acquire_restore = lock._acquire_restore if hasattr(lock, '_acquire_restore')
        self._is_owned = lock._is_owned if hasattr(lock, '_is_owned')
        self._waiters = _deque()

I don’t know how other readers see it, but I look at both versions and the 2nd one seems a lot cleaner and easier to understand. The number of lines is of course not relevant.

Even the assignment self._lock = lock can now be re-written to be part of the 1st statement.

You’d also need to consider the fact that

retries -= 1 if logical_condition else 2

is currently valid syntax, and both the parser and human readers need to look ahead quite a long way to distinguish the two cases, and that often hurts readability. Particularly if logical_condition is a complex expression (which it typically is, from my experience with Perl, as that’s when you want to push it to the end, to avoid having it “hide” the main point of the construct, which is to decrement retries).

Considered from the very beginning of the idea. I do actually (and truly) believe that it is an extension of that syntax, which allows dropping the else 2 (in the example)

The ternary assigment without else (call it "dernary" for marketing purposes) is what kept banging in my head whenever I felt that a return following an if could be read a lot more naturally by writing first the return and then the logical condition to be evaluated, which would determine the execution of the return.

I dislike this because it makes it difficult to tell at a glance whether a variable is defined in a block of code. Currently, if a line starts with retries =, you know that later in the same block, that variable will definitely exist. With this proposed syntax, you have to scan for whether there’s an if somewhere in the middle of the expression.

In that particular example: retries must already exist or else the -= will already generate an exception.

This other example is different:

retries = 0 if logical_condition

Indeed: the variable may or may not be defined by the expression. In this case, the AST should “conditionally” define the variable. If upon exiting the scope in which the variable exists the variable is used with no other fixed definition (ex-ante, ex-post) having taking place.

For return (and other) statements the logic is a lot clearer. It is only executed if the logical_condition evaluates to True

I would assume that it’s semantically equivalent to if logical_condition: retries = 0 - is that not the case?

That’s all explained in the initial post. Whether it is a one-liner or not it’s not the point. It’s not about one-liners, it’s about turning around how the syntax is read to make it more natural.

For the sake of it:

return if not q.qsize()

Which would only execute the return if the size of a queue.Queue (or something supporting a similar API) is zero.

Yes it can also be written as:

if not q.qsize():
    return

(which can of course be a one-liner even if not PEP-8 compliant and formatters will reformat it)

but after years of typing the latter, my python-soul has the impression that writing the former would be more natural, expressive and clearer.

For many years I have defended Python’s ternary operator value if condition else alternative on the basis that this is very close to the English

“I’m going to the movies tonight, if I get home early, otherwise I’ll just watch a DVD.”

I still stand by that. English does allow the conditional “if” clause to appear second.

But I don’t think even the most enthusiastic defenders of ternary-if say that this form is the most natural format, only that it isn’t as awful as the opponents pretend.

There is a good reason why, out of hundreds of programming languages in the world, only a handful mess with the standard if condition then A else B syntax – and most of those are concatenative languages using Reverse Polish Notation with a stack, like Forth and Factor. Failing a good reason to change the order, the most natural order is to put the decision first, then the alternatives.

(At least for languages based on English-like grammar. Other natural languages may differ.)

If you think that result = expression if condition is more readable and natural than if condition: result = expression, I think that this is an idiosyncratic preference that very few people will share.

There are also practical issues with this suggestion:

Is this an expression or a statement?

If it is an expression then it has to have a value regardless of whether the condition is true or not:

print(("Hello" if condition).upper(), "WORLD")

will print “HELLO WORLD” if the condition is true, but what will it print if it is false? Expressions can’t just evaluate to nothing at all.

If it is a statement, we will need to modify the grammar rules to allow an optional if condition. So we have to work out where it is allowed. Some are obvious:

return expression [if condition]  #	[...] means optional
expression [if condition]
result = expression [if condition]
result += expression [if condition]  # and likewise for other augmented assignments

But where else might it go?

import name [if condition]
del namelist [if condition]

not to mention weirder possibilities:

try [if condition] :
    block
except ...

def function_name(parameterlist) [-> return_annotation] [if condition] :
    block

for targetlist in iterable [if condition] :
    block

etc. Note that the last example conflicts with another often-requested feature, “Let’s save one line and an indent by allowing if with for loops like in comprehensions”.

for x in sequence if cond: ...

could be syntactic sugar for either of these:

if cond:
    for x in sequence: ...

for x in sequence:
    if cond: ...

Either way, all of these conditionals require the grammar rules to be updated to allow an optional if.

Or… we can just stick to the status quo, and compose statements. As you acknowledge, this does nothing to the language that we don’t already have by composing an if statement with whatever you want. It literally adds zero new functionality to Python and allows us to do nothing we couldn’t already do.

This discussion is purely an argument about the colour of the bikeshed: should we keep the existing colour if condition: code or can we paint it the fancy new colour code if condition?

3 Likes

It’d definitely be a statement, although one form of simple statement is the expression statement, so for instance print(...) if debug would be fully valid (the statement happens to be an expression).

The usual reason for having this sort of thing in a language is that it puts the action front and center, instead of the condition. A common example is this sort of “if debug” check, where you would put the focus on the actual output call, and only as a minor footnote, show whether it will happen or not. It’s not just about the colour of your bikeshed; it’s like everything else where there are multiple spellings, and the programmer is allowed to emphasize the important parts.

Personally, I don’t think it’s necessary, and I also don’t much like it; but that’s a matter of opinion.