`if-unless` expressions in Python

`if-unless` expressions in Python if condition1 expr unless condition2 is an expression that roughly reduces to expr if condition1 and not condition2 else EMPTY This definition means that expr is only evaluated if `condition1 and not condition2` evaluates to true. It also means `not condition2` is only evaluated if `condition1` is true. # EMPTY EMPTY is not actually a real Python value-- it's a value that collapses into nothing when used inside a statement expression: print([ if False never_called() unless False, if False never_called() unless False, ]) # => [] print([ 3, if False never_called() unless False, if False never_called() unless False, 2, if True 5 unless False, 4 ]) # => [3, 2, 5, 4] EMPTY is neither a constant exposed to the Python runtime nor a symbol. It's a compiler-internal value. # Use cases Assertions. assert if condition1 predicate(object) unless condition2 (This would be more readable with assert expressions.) Macros. # Equivalent syntax in existing Python As a statement: if condition1 and not condition2: predicate(object) predicate(object) if condition1 and not condition2 # Backward compatibility The `unless` word is only recognized as special inside `if-unless` statements. The continued use of the word as a variable name is discouraged.

On 2019-06-04 01:57, James Lu wrote:
-1 I find it very difficult to understand. For statements we already have the 'if' statement, as above. Within expressions such as a list comprehension, it would be much clearer IMHO to have a ternary 'if' with 'pass': expr if condition else pass No new reserved words needed.

On Tue, Jun 4, 2019 at 10:58 AM James Lu <jamtlu@gmail.com> wrote:
Why the double condition? The existing definition of "condition1 and not condition2" already guarantees the short-circuiting, and what you're effectively creating is two pieces of a single condition.
Okay, now this starts to look somewhat interesting. But what you'd be creating here would be a feature of specific language constructs, and probably wouldn't work everywhere.
Honestly not sure what this does that's better than ordinary expressions. Can you give some concrete examples of how you would use this, and what the equivalent code for current Pythons would look like?
Adding "else None" would make this legal Python syntax right now, but for very good reasons, is not how most people write code. So, looking just at the "EMPTY" part. Allow me to reword your proposal into what I think you're saying; is this what you're proposing? ## New "unless" construct for list displays and argument lists ## Inside a list/dict/set/tuple display, or inside an argument list, elements can be conditionally omitted by providing a predicate. lst = [f1(), f3() unless f2(), f4()] The functions will be called in the order indicated. If f2() returns a falsey value, f3() will not be evaluated, and the list will have only two elements in it. ##### That's without the two-part condition. You can always put "expr unless x and not y" if you need both parts. How close is this to what you were thinking of? ChrisA

Chris Angelico writes:
Not a fan of this in displays, YMMV. IME the cases where I could use this are generally well-served by comprehensions with if clauses. (I'm speaking for myself only, I have no idea if others have "more than once in a blue moon" use cases.) OTOH foo(f1(), f3() unless f2(), f4()) looks horrible to me, a definite -1. Yes, I know about varargs functions, but "positional" means positional to me. Surely this would almost always be a runtime error in format() if not f2(), for example. It would have to be used quite frequently for me to get over the awkwardness, I think. Granted, foo(*[f1(), f3() unless f2(), f4()]) is horrid, too (and if you wanted to use a tuple for efficiency it would be unreadable). But how often would you want to call varargs with arguments that might not even be there? Would args = (f1(), f3() unless f2(), f4()) foo(*args) be so bad for the rare occasion? (Maybe it's not so rare for others, but I can't recall ever wanting this, while list displays with variable desired length do come up.) Steve

Do you mean this ?Currently what I use is the `*` operator on lists : ``` print([ 3, ] + ([never_called()] if False else []) + [ 2, ] + ([5] if True else []) + [ 4 ]) # => [3, 2, 5, 4] ``` (put the whitespaces where you want) And if the call to `never_called()` is not important (no need for shortcircuit) : ``` print([ 3, ]+ [ never_called() ] * False + [ 2, ] + [ 5 ] * True + [ 4 ]) # => [3, 2, 5, 4] ``` (put the whitespaces where you want) You could do things like L = [4,5,2] + ['hello', world'] * (a == 5) + [8,9] Of course, that's not as optimised as : L = [] L += [4,5,2] if a == 5: L += ['hello', world'] L += [8,9]

Apologies for the inactive post.
How close is this to what you were thinking of? The conditional omission part is exactly what I'm thinking of.
I made a mistake in my original proposal. To keep all the information organized, I'm going to keep a canonical version of the proposal on the link below. https://hackmd.io/CDqvdxF_QY-STrz0wQy83g Please give it a new read. I tried to make it as clear as possible in this new version. Here are the revisions I've made compared to the original thanks to your discussions. That's without the two-part condition. You can always put "expr unless x and not y" if you need both parts.

On 2019-06-23 19:49, James Lu wrote:
You wrote: """ if condition1 expr unless condition2 is an expression that roughly reduces to expr if not condition1 and condition2 else EMPTY """ Surely it's: """ if condition1 expr unless condition2 is an expression that roughly reduces to expr if condition1 and not condition2 else EMPTY """ Another point is that you have condition1 and expr next to each other with no separating punctuation, which looks unPythonic to me. Under "Equivalent syntax in existing Python" you wrote: """ As a statement: if condition1 and not condition2: predicate(object) predicate(object) if condition1 and not condition2 """ but only the first is valid as a statement. Finally, under "For consideration: alternative syntaxes", my offering would be: expr if condition1 and not condition2 else pass [snip]

On Jun 23, 2019, at 13:33, MRAB <python@mrabarnett.plus.com> wrote:
Finally, under "For consideration: alternative syntaxes", my offering would be:
expr if condition1 and not condition2 else pass
This seems a lot more tenable than the original proposal. The “unless” seems both unnecessary and overly restrictive. Being able to specify “no argument/container element” in an expression is what the OP is really looking for, and spelling that “pass” makes sense, and then putting it inside the existing conditional expression gives the exact behavior the original example wanted without anything else new, ambiguous, or potentially confusing. But I think it still needs to be fleshed out. I get the feeling you aren’t seriously proposing this change as something you want, but if the OP agrees that it gives him everything he wants, maybe he’ll be willing to think through all the details. And anyway, most of the same questions apply to the OP’s proposal, they’re just a bit harder to get at. The big question is: if “pass” (or EMPTY) is allowed in (some) expressions, what happens if you use it somewhere illegal? For example, given than “1 if spam else pass” is a legal construct, is “return 1 if spam else pass” a SyntaxError (because that legal construct is something other than an expression), or is that a syntactically valid expression in a valid statement that just raises some kind of runtime error if the expression evaluates to “pass”, because return needs a value to return? If it’s a syntax error, I think you really need to work through the language grammar and show what needs to change. My worry is that it would require duplicating half the nodes in the grammar (unless you literally can’t embed conditional-pass-expressions in any other construct except directly in an argument list or starred list—I assume you’d want to be able to put a conditional-pass inside parens, or inside another conditional-pass, if nothing else…), but I could easily be wrong. If it’s a runtime error, the grammar seems a lot simpler—just make “pass” a special keyword identifier like None or False and I think you’re done (presumably remove the pass statement, too)—but the semantics are more complicated. Even forgetting about statements, what does it mean to yield an expression that evaluates to pass, or to lambda one? Also, even within argument lists, container displays, and expression lists used for tuple values, I think there are some unclear things: can keyword-argument values, dict display keys and/or values, and starred and double-starred items be pass-valued, and, if so, what does that mean for each? Meanwhile, although argument lists and container displays were the original desired use cases (and presumably also expression lists used as “tuple displays”), the OP actually wanted this to be used in “any statement expression”, and gave an assert statement as an example. I’m not quite sure what it’s supposed to do there (does it skip the assert? if so, how is that different from asserting True anyway?), and even less sure how to extend that idea to all statements that use an expression anywhere. Is this actually a necessary part of the feature? If so, there’s obviously even more work to be done defining the syntax and/or semantics. Finally, it would be nice to see each example compared to the best way to do the same thing (presumably with iterable unpacking) in current Python, to see how much It actually improves readability. The only equivalents given by the OP are a case where you can just use an if statement (which looks better than the proposed new syntax—presumably the whole point of this is for cases where you can’t easily do that, as with arguments and list elements) and an expression that isn’t actually equivalent (if it were, this feature wouldn’t be needed at all).

On 2019-06-24 02:43, Andrew Barnert wrote:
As the "pass" indicates omission, it could really only be valid where an expression (possibly followed by a comma) can be omitted, which is as the top-level expression. For: return expr if condition else pass When condition is true: return expr When condition is false: return On the other hand, that would give the same result as: return expr if condition else None which is already valid, and no longer. What about "yield"? The same? So, I can see a use for it only in places such as a tuple, list or set display, or a positional argument in an argument list. Not sure about a dict display or a keyword argument; that might be going too far!

On Jun 23, 2019, at 19:57, MRAB <python@mrabarnett.plus.com> wrote:
So that means you can’t combine a conditional pass expression into anything larger. You can’t even put it in parentheses. That already makes it seem a lot less usable, and makes it smell suspect as a concept too. Also, it feels very strange that “pass” can be used as the else-value of a conditional expression, but not in any other kind of expression. Also, at the statement level, you can already always trivially rewrite things, probably more readably, as an if statement. It’s only inside expression lists and expressions, where you can’t do so, that the feature seems at all compelling. Plus, I don’t think it really works.
You’re getting two syntactically different statement forms, depending on the runtime value of condition. Of course that’s not impossible to implement, but it seems weird. But the real problem is that return is one of the only places in the language that actually works this way. Almost every other place you can have an expression in a statement, either the expression is mandatory (e.g., you can’t write x = with nothing on the right, so if that’s what x = 2 if cond else pass means when not cond, what? a SyntaxError at runtime?), or leaving it out doesn’t mean “use a sensible default value” but “do something different” (e.g., raise MyException(stuff) if cond else pass). Also, what about expression statements? Under your rule, there’s just no statement at all if not cond (possibly leading to an IndentError at runtime?). Meanwhile, this doesn’t work for the OP’s only statement example: assert spam if eggs else pass should not be the same as assert (a SyntaxError), or assert None (which raises an AssertionError because None is falsely), but do nothing at all. Which makes me think that maybe he’d expect your return statement to not return, rather than to return None.
What about "yield"? The same?
Well, yield isn’t a statement, it’s an expression, so that already falls under your “only the top-level expression in a statement” rule.
So, I can see a use for it only in places such as a tuple, list or set display, or a positional argument in an argument list.
The OP says it’s useful in assert. But I agree with you, all the potentially good uses seem to be when the expression is part of comma-separated list of expressions. Which I think means only container displays, expression lists (which usually work as “tuple displays”, but it’s a separate piece of syntax, and you have to be careful with the 0- and 1-element cases), and argument lists. But someone still needs to work out exactly what that means, and (if it’s going to be enforced syntactically rather than at runtime) what it does to the grammar. Because, again, I worry that the only way to allow, e.g., a starred_list to include pass elements is to fork half the nodes in the grammar underneath it.
Not sure about a dict display or a keyword argument; that might be going too far!
It seems to me that there’s no reason to ban starred items and keyword arguments if they fall naturally out of the change, but no reason to go out of the way to add them if they don’t. The semantics should be pretty obvious to any reader, but there’s also an existing simple way to write it (*pass clearly should do the same thing as *(), inserting zero positional arguments, right?). But dict displays, that could be confusing. Do you have to pass-value the key, or the value, or either of the two, or both consistently? If the key, does that short-circuit the value expression? So I think you’re right, that’s possibly worth banning even if you have to go out of your way to do so.

On Mon, Jun 24, 2019 at 2:44 PM Andrew Barnert via Python-ideas <python-ideas@python.org> wrote:
But dict displays, that could be confusing. Do you have to pass-value the key, or the value, or either of the two, or both consistently? If the key, does that short-circuit the value expression? So I think you’re right, that’s possibly worth banning even if you have to go out of your way to do so.
I think if this could be made to work for list displays and function arguments, it wouldn't be too hard to settle the semantics for a dict. My personal shed colour would be: omit the key and the value won't be evaluated; omit either to suppress the result. But there are other valid semantics, and it'd just be a matter of picking something sane. The biggest problem with this proposal is the way that, being a syntactic construct, it's going to be non-composable. # Oops, syntax error with (some_expr as q, some_other_expr as w): pass ChrisA

On Mon, Jun 24, 2019 at 6:03 PM Andrew Barnert <abarnert@yahoo.com> wrote:
Sorry, I kinda glossed over a lot of details there. The example that I gave is a SyntaxError, unrelated to the current proposal. To make it work, you have to remove the parens, and use a backslash to continue the line. And every now and then, someone proposes to make the 'with' statement accept the form I gave, because it would be cleaner. It isn't an easy change, because "with expr as name" is a syntactic construct; the "expr as name" part cannot be composed into a larger expression. The proposed syntax would have the exact same problem: stuff = [ foo unless True, bar unless False, (quux("spam", "eggs", "sausage", "spam") unless customer_wants("spam")) ] This particular example would work if you just removed the parens (it doesn't need a backslash to continue the line, since it's inside a list display), but I can pretty much guarantee that people will put parens around one of these sequences and then wonder why on earth it doesn't work. ChrisA

On 2019-06-24 09:23, Chris Angelico wrote:> On Mon, Jun 24, 2019 at 6:03 PM Andrew Barnert <abarnert@yahoo.com> wrote:
Well, I suppose it could work with parentheses, but not necessarily in the way you want/expect. If customer_wants("spam") returns False, then (quux("spam", "eggs", "sausage", "spam") unless customer_wants("spam")) could mean ()! (You could say that it doesn't, and if you want that behaviour then you should add a trailing comma.)

On Mon, Jun 24, 2019 at 10:38 PM MRAB <python@mrabarnett.plus.com> wrote:
I definitely don't want that (and I'm not particularly in favour of the original proposal anyway). In any case, (x) isn't a tuple, so it doesn't make sense to have it become one by the omission of its inner value; your parenthesized comment is the better option here, but has the same complexity that I was talking about. ChrisA

On 2019-06-24 05:39, Andrew Barnert wrote:
I'm now wondering whether we need the "else pass" part at all. Currently the ternary 'if' expects an 'else' after the condition, but in a display a unary 'if' (should we call it that?) would be followed by ',' or 'for' or the end of the display. Incidentally, I don't think a unary 'if' should skip a statement if the condition is false; that's what an 'if' _statement_ is for! I'm not convinced of its usefulness anyway except as a way of omitting an item from a series of items.

Folks, Can I please remind people to trim their quoting? We readers shouldn't have to hit the Page Down key eight or nine times, or scroll down through five or six pages, to see the first new content. (I have a rule: if I haven't reached new content after nine screens of quoting, I give up and delete the email.) Quoting is supposed to show context, not to be a full record of the entire history of the thread. We have archives for that :) If you really can't trim the quoting (backspace key broken? feeble email client?) then speaking for myself, I'd rather you top-post so at least I can see your comments within a page or two of the start of the email. Thank you, Steven

I think print("Remember to wear sunscreen!") if hot_outside unless Weather.is_raining() Is more readable than if hot_outside and not Weather.is_raining(): print("Remember to wear sunscreen!") I think the unless syntax would be especially useful when there is a series of functions like this. The general use case would be: side_effecting_expression if frequent_condition unless rare_condition

On 6/24/19 12:32 PM, James Lu wrote:
I disagree, but that's bound to happen in a discussion like this.
Can you clarify "series of functions like this"? Are the functions the calls to print? Are the functions the calls to Weather.is_raining()? IMO, in a "series of logic like this," the fact that individual bits are the same should be blindingly obvious. With line after line of multiple bits (some side-effecting statement, two conditions, and some keywords), as opposed to a series of if statements with their associated inddented suites, I feel that the commonality will get lost.

On Jun 24, 2019, at 10:27, Dan Sommers <2QdxY4RzWzUUiLuE@potatochowder.com> wrote:
I also disagree. And I think there’s a principled reason for it. In a language like Perl, where there’s little consistency in syntax and flow control is scattered all over the place, this would be great. You can write the code in the same order as the sentence you’re thinking in your native language. In fact, Perl added postfix conditionals back in version 4 or so, and everyone loved it. But in Python, flow control is restricted to compound statements, which all have the same form: a colon and an indebted block. This makes Python a lot easier to read. While it’s _possible_ to escape from an expression by intentionally calling a function that raises, or to bury side-effect-only function calls deep in an expression, it’s considered bad style, and only done in rare, exceptional cases. What’s being proposed here is to add a new way to do that. So, either it’s deliberately encouraging bad style, or it’s something that would be so rarely used that it’s not worth it. What about comprehensions? They _do_ have flow control within an expression, and even spell it with a postfix condition. But comprehensions are a highly restricted mini-language, where the only flow control possible is skipping, and the syntax encourages you to keep them short and simple. So they turn out to be worth it. Which is exactly why the more restricted version of the proposal—only allowing “pass” (or however it’s spelled) inside displays (and “tuple displays”) and argument lists—might be worth it, even if the fully general one isn’t. That being said, I still haven’t seen an example of a function call or list display that actually looks compelling. I think if anyone wants to convince everyone that some form of this proposal is worthwhile, that’s the first thing they need to provide.

I'm concerned that in this thread, and elsewhere, we're not paying sufficient attention to https://en.wikipedia.org/wiki/Cognitive_load. Adding a feature to a system can increase the cognitive load in learning the system, and it can also reduce the cognitive load in using the system. Sometimes adding a feature can reduce cognitive load in both learning and using the system, but I suspect that is exceptional. I suggest that in this thread we have more discussion of the cognitive load adding this feature would add to those learning the system. I also suggest, as someone else has already done, that we focus more on the problem to be solved rather than a specific solution. -- Jonathan

On 24/06/2019 20:50, Jonathan Fine wrote:
I'm concerned that in this thread, and elsewhere, we're not paying sufficient attention to https://en.wikipedia.org/wiki/Cognitive_load.
I was under the impression that most of the counter-arguments could be rephrased as "this increases cognitive load for no clear benefit" myself. A sentiment I agree with, by the way. -- Rhodri James *-* Kynesim Ltd

On Mon, Jun 03, 2019 at 08:57:22PM -0400, James Lu wrote:
Then the "unless" clause is superfluorous and we can write: if condition1 and not condition2 expression which is another way of saying if condition expression which has been suggested before in the form: expression if condition and rejected. Please check the archives.
Which is precisely how "and" already works.
I don't even know what you mean by that even after reading your examples, sorry. If I do this: var = if False 1 unless False print(var) what happens? -- Steven

On 6/3/2019 8:57 PM, James Lu wrote:
An expression MUST evaluate to a value. EMPTY is spelled None in Python. No need for an alternate spelling. The only place that None is implicit rather than explicit is the implicit 'return None' *statement* at the end of functions. That is enough. -- Terry Jan Reedy

On 2019-06-04 01:57, James Lu wrote:
-1 I find it very difficult to understand. For statements we already have the 'if' statement, as above. Within expressions such as a list comprehension, it would be much clearer IMHO to have a ternary 'if' with 'pass': expr if condition else pass No new reserved words needed.

On Tue, Jun 4, 2019 at 10:58 AM James Lu <jamtlu@gmail.com> wrote:
Why the double condition? The existing definition of "condition1 and not condition2" already guarantees the short-circuiting, and what you're effectively creating is two pieces of a single condition.
Okay, now this starts to look somewhat interesting. But what you'd be creating here would be a feature of specific language constructs, and probably wouldn't work everywhere.
Honestly not sure what this does that's better than ordinary expressions. Can you give some concrete examples of how you would use this, and what the equivalent code for current Pythons would look like?
Adding "else None" would make this legal Python syntax right now, but for very good reasons, is not how most people write code. So, looking just at the "EMPTY" part. Allow me to reword your proposal into what I think you're saying; is this what you're proposing? ## New "unless" construct for list displays and argument lists ## Inside a list/dict/set/tuple display, or inside an argument list, elements can be conditionally omitted by providing a predicate. lst = [f1(), f3() unless f2(), f4()] The functions will be called in the order indicated. If f2() returns a falsey value, f3() will not be evaluated, and the list will have only two elements in it. ##### That's without the two-part condition. You can always put "expr unless x and not y" if you need both parts. How close is this to what you were thinking of? ChrisA

Chris Angelico writes:
Not a fan of this in displays, YMMV. IME the cases where I could use this are generally well-served by comprehensions with if clauses. (I'm speaking for myself only, I have no idea if others have "more than once in a blue moon" use cases.) OTOH foo(f1(), f3() unless f2(), f4()) looks horrible to me, a definite -1. Yes, I know about varargs functions, but "positional" means positional to me. Surely this would almost always be a runtime error in format() if not f2(), for example. It would have to be used quite frequently for me to get over the awkwardness, I think. Granted, foo(*[f1(), f3() unless f2(), f4()]) is horrid, too (and if you wanted to use a tuple for efficiency it would be unreadable). But how often would you want to call varargs with arguments that might not even be there? Would args = (f1(), f3() unless f2(), f4()) foo(*args) be so bad for the rare occasion? (Maybe it's not so rare for others, but I can't recall ever wanting this, while list displays with variable desired length do come up.) Steve

Do you mean this ?Currently what I use is the `*` operator on lists : ``` print([ 3, ] + ([never_called()] if False else []) + [ 2, ] + ([5] if True else []) + [ 4 ]) # => [3, 2, 5, 4] ``` (put the whitespaces where you want) And if the call to `never_called()` is not important (no need for shortcircuit) : ``` print([ 3, ]+ [ never_called() ] * False + [ 2, ] + [ 5 ] * True + [ 4 ]) # => [3, 2, 5, 4] ``` (put the whitespaces where you want) You could do things like L = [4,5,2] + ['hello', world'] * (a == 5) + [8,9] Of course, that's not as optimised as : L = [] L += [4,5,2] if a == 5: L += ['hello', world'] L += [8,9]

Apologies for the inactive post.
How close is this to what you were thinking of? The conditional omission part is exactly what I'm thinking of.
I made a mistake in my original proposal. To keep all the information organized, I'm going to keep a canonical version of the proposal on the link below. https://hackmd.io/CDqvdxF_QY-STrz0wQy83g Please give it a new read. I tried to make it as clear as possible in this new version. Here are the revisions I've made compared to the original thanks to your discussions. That's without the two-part condition. You can always put "expr unless x and not y" if you need both parts.

On 2019-06-23 19:49, James Lu wrote:
You wrote: """ if condition1 expr unless condition2 is an expression that roughly reduces to expr if not condition1 and condition2 else EMPTY """ Surely it's: """ if condition1 expr unless condition2 is an expression that roughly reduces to expr if condition1 and not condition2 else EMPTY """ Another point is that you have condition1 and expr next to each other with no separating punctuation, which looks unPythonic to me. Under "Equivalent syntax in existing Python" you wrote: """ As a statement: if condition1 and not condition2: predicate(object) predicate(object) if condition1 and not condition2 """ but only the first is valid as a statement. Finally, under "For consideration: alternative syntaxes", my offering would be: expr if condition1 and not condition2 else pass [snip]

On Jun 23, 2019, at 13:33, MRAB <python@mrabarnett.plus.com> wrote:
Finally, under "For consideration: alternative syntaxes", my offering would be:
expr if condition1 and not condition2 else pass
This seems a lot more tenable than the original proposal. The “unless” seems both unnecessary and overly restrictive. Being able to specify “no argument/container element” in an expression is what the OP is really looking for, and spelling that “pass” makes sense, and then putting it inside the existing conditional expression gives the exact behavior the original example wanted without anything else new, ambiguous, or potentially confusing. But I think it still needs to be fleshed out. I get the feeling you aren’t seriously proposing this change as something you want, but if the OP agrees that it gives him everything he wants, maybe he’ll be willing to think through all the details. And anyway, most of the same questions apply to the OP’s proposal, they’re just a bit harder to get at. The big question is: if “pass” (or EMPTY) is allowed in (some) expressions, what happens if you use it somewhere illegal? For example, given than “1 if spam else pass” is a legal construct, is “return 1 if spam else pass” a SyntaxError (because that legal construct is something other than an expression), or is that a syntactically valid expression in a valid statement that just raises some kind of runtime error if the expression evaluates to “pass”, because return needs a value to return? If it’s a syntax error, I think you really need to work through the language grammar and show what needs to change. My worry is that it would require duplicating half the nodes in the grammar (unless you literally can’t embed conditional-pass-expressions in any other construct except directly in an argument list or starred list—I assume you’d want to be able to put a conditional-pass inside parens, or inside another conditional-pass, if nothing else…), but I could easily be wrong. If it’s a runtime error, the grammar seems a lot simpler—just make “pass” a special keyword identifier like None or False and I think you’re done (presumably remove the pass statement, too)—but the semantics are more complicated. Even forgetting about statements, what does it mean to yield an expression that evaluates to pass, or to lambda one? Also, even within argument lists, container displays, and expression lists used for tuple values, I think there are some unclear things: can keyword-argument values, dict display keys and/or values, and starred and double-starred items be pass-valued, and, if so, what does that mean for each? Meanwhile, although argument lists and container displays were the original desired use cases (and presumably also expression lists used as “tuple displays”), the OP actually wanted this to be used in “any statement expression”, and gave an assert statement as an example. I’m not quite sure what it’s supposed to do there (does it skip the assert? if so, how is that different from asserting True anyway?), and even less sure how to extend that idea to all statements that use an expression anywhere. Is this actually a necessary part of the feature? If so, there’s obviously even more work to be done defining the syntax and/or semantics. Finally, it would be nice to see each example compared to the best way to do the same thing (presumably with iterable unpacking) in current Python, to see how much It actually improves readability. The only equivalents given by the OP are a case where you can just use an if statement (which looks better than the proposed new syntax—presumably the whole point of this is for cases where you can’t easily do that, as with arguments and list elements) and an expression that isn’t actually equivalent (if it were, this feature wouldn’t be needed at all).

On 2019-06-24 02:43, Andrew Barnert wrote:
As the "pass" indicates omission, it could really only be valid where an expression (possibly followed by a comma) can be omitted, which is as the top-level expression. For: return expr if condition else pass When condition is true: return expr When condition is false: return On the other hand, that would give the same result as: return expr if condition else None which is already valid, and no longer. What about "yield"? The same? So, I can see a use for it only in places such as a tuple, list or set display, or a positional argument in an argument list. Not sure about a dict display or a keyword argument; that might be going too far!

On Jun 23, 2019, at 19:57, MRAB <python@mrabarnett.plus.com> wrote:
So that means you can’t combine a conditional pass expression into anything larger. You can’t even put it in parentheses. That already makes it seem a lot less usable, and makes it smell suspect as a concept too. Also, it feels very strange that “pass” can be used as the else-value of a conditional expression, but not in any other kind of expression. Also, at the statement level, you can already always trivially rewrite things, probably more readably, as an if statement. It’s only inside expression lists and expressions, where you can’t do so, that the feature seems at all compelling. Plus, I don’t think it really works.
You’re getting two syntactically different statement forms, depending on the runtime value of condition. Of course that’s not impossible to implement, but it seems weird. But the real problem is that return is one of the only places in the language that actually works this way. Almost every other place you can have an expression in a statement, either the expression is mandatory (e.g., you can’t write x = with nothing on the right, so if that’s what x = 2 if cond else pass means when not cond, what? a SyntaxError at runtime?), or leaving it out doesn’t mean “use a sensible default value” but “do something different” (e.g., raise MyException(stuff) if cond else pass). Also, what about expression statements? Under your rule, there’s just no statement at all if not cond (possibly leading to an IndentError at runtime?). Meanwhile, this doesn’t work for the OP’s only statement example: assert spam if eggs else pass should not be the same as assert (a SyntaxError), or assert None (which raises an AssertionError because None is falsely), but do nothing at all. Which makes me think that maybe he’d expect your return statement to not return, rather than to return None.
What about "yield"? The same?
Well, yield isn’t a statement, it’s an expression, so that already falls under your “only the top-level expression in a statement” rule.
So, I can see a use for it only in places such as a tuple, list or set display, or a positional argument in an argument list.
The OP says it’s useful in assert. But I agree with you, all the potentially good uses seem to be when the expression is part of comma-separated list of expressions. Which I think means only container displays, expression lists (which usually work as “tuple displays”, but it’s a separate piece of syntax, and you have to be careful with the 0- and 1-element cases), and argument lists. But someone still needs to work out exactly what that means, and (if it’s going to be enforced syntactically rather than at runtime) what it does to the grammar. Because, again, I worry that the only way to allow, e.g., a starred_list to include pass elements is to fork half the nodes in the grammar underneath it.
Not sure about a dict display or a keyword argument; that might be going too far!
It seems to me that there’s no reason to ban starred items and keyword arguments if they fall naturally out of the change, but no reason to go out of the way to add them if they don’t. The semantics should be pretty obvious to any reader, but there’s also an existing simple way to write it (*pass clearly should do the same thing as *(), inserting zero positional arguments, right?). But dict displays, that could be confusing. Do you have to pass-value the key, or the value, or either of the two, or both consistently? If the key, does that short-circuit the value expression? So I think you’re right, that’s possibly worth banning even if you have to go out of your way to do so.

On Mon, Jun 24, 2019 at 2:44 PM Andrew Barnert via Python-ideas <python-ideas@python.org> wrote:
But dict displays, that could be confusing. Do you have to pass-value the key, or the value, or either of the two, or both consistently? If the key, does that short-circuit the value expression? So I think you’re right, that’s possibly worth banning even if you have to go out of your way to do so.
I think if this could be made to work for list displays and function arguments, it wouldn't be too hard to settle the semantics for a dict. My personal shed colour would be: omit the key and the value won't be evaluated; omit either to suppress the result. But there are other valid semantics, and it'd just be a matter of picking something sane. The biggest problem with this proposal is the way that, being a syntactic construct, it's going to be non-composable. # Oops, syntax error with (some_expr as q, some_other_expr as w): pass ChrisA

On Mon, Jun 24, 2019 at 6:03 PM Andrew Barnert <abarnert@yahoo.com> wrote:
Sorry, I kinda glossed over a lot of details there. The example that I gave is a SyntaxError, unrelated to the current proposal. To make it work, you have to remove the parens, and use a backslash to continue the line. And every now and then, someone proposes to make the 'with' statement accept the form I gave, because it would be cleaner. It isn't an easy change, because "with expr as name" is a syntactic construct; the "expr as name" part cannot be composed into a larger expression. The proposed syntax would have the exact same problem: stuff = [ foo unless True, bar unless False, (quux("spam", "eggs", "sausage", "spam") unless customer_wants("spam")) ] This particular example would work if you just removed the parens (it doesn't need a backslash to continue the line, since it's inside a list display), but I can pretty much guarantee that people will put parens around one of these sequences and then wonder why on earth it doesn't work. ChrisA

On 2019-06-24 09:23, Chris Angelico wrote:> On Mon, Jun 24, 2019 at 6:03 PM Andrew Barnert <abarnert@yahoo.com> wrote:
Well, I suppose it could work with parentheses, but not necessarily in the way you want/expect. If customer_wants("spam") returns False, then (quux("spam", "eggs", "sausage", "spam") unless customer_wants("spam")) could mean ()! (You could say that it doesn't, and if you want that behaviour then you should add a trailing comma.)

On Mon, Jun 24, 2019 at 10:38 PM MRAB <python@mrabarnett.plus.com> wrote:
I definitely don't want that (and I'm not particularly in favour of the original proposal anyway). In any case, (x) isn't a tuple, so it doesn't make sense to have it become one by the omission of its inner value; your parenthesized comment is the better option here, but has the same complexity that I was talking about. ChrisA

On 2019-06-24 05:39, Andrew Barnert wrote:
I'm now wondering whether we need the "else pass" part at all. Currently the ternary 'if' expects an 'else' after the condition, but in a display a unary 'if' (should we call it that?) would be followed by ',' or 'for' or the end of the display. Incidentally, I don't think a unary 'if' should skip a statement if the condition is false; that's what an 'if' _statement_ is for! I'm not convinced of its usefulness anyway except as a way of omitting an item from a series of items.

Folks, Can I please remind people to trim their quoting? We readers shouldn't have to hit the Page Down key eight or nine times, or scroll down through five or six pages, to see the first new content. (I have a rule: if I haven't reached new content after nine screens of quoting, I give up and delete the email.) Quoting is supposed to show context, not to be a full record of the entire history of the thread. We have archives for that :) If you really can't trim the quoting (backspace key broken? feeble email client?) then speaking for myself, I'd rather you top-post so at least I can see your comments within a page or two of the start of the email. Thank you, Steven

I think print("Remember to wear sunscreen!") if hot_outside unless Weather.is_raining() Is more readable than if hot_outside and not Weather.is_raining(): print("Remember to wear sunscreen!") I think the unless syntax would be especially useful when there is a series of functions like this. The general use case would be: side_effecting_expression if frequent_condition unless rare_condition

On 6/24/19 12:32 PM, James Lu wrote:
I disagree, but that's bound to happen in a discussion like this.
Can you clarify "series of functions like this"? Are the functions the calls to print? Are the functions the calls to Weather.is_raining()? IMO, in a "series of logic like this," the fact that individual bits are the same should be blindingly obvious. With line after line of multiple bits (some side-effecting statement, two conditions, and some keywords), as opposed to a series of if statements with their associated inddented suites, I feel that the commonality will get lost.

On Jun 24, 2019, at 10:27, Dan Sommers <2QdxY4RzWzUUiLuE@potatochowder.com> wrote:
I also disagree. And I think there’s a principled reason for it. In a language like Perl, where there’s little consistency in syntax and flow control is scattered all over the place, this would be great. You can write the code in the same order as the sentence you’re thinking in your native language. In fact, Perl added postfix conditionals back in version 4 or so, and everyone loved it. But in Python, flow control is restricted to compound statements, which all have the same form: a colon and an indebted block. This makes Python a lot easier to read. While it’s _possible_ to escape from an expression by intentionally calling a function that raises, or to bury side-effect-only function calls deep in an expression, it’s considered bad style, and only done in rare, exceptional cases. What’s being proposed here is to add a new way to do that. So, either it’s deliberately encouraging bad style, or it’s something that would be so rarely used that it’s not worth it. What about comprehensions? They _do_ have flow control within an expression, and even spell it with a postfix condition. But comprehensions are a highly restricted mini-language, where the only flow control possible is skipping, and the syntax encourages you to keep them short and simple. So they turn out to be worth it. Which is exactly why the more restricted version of the proposal—only allowing “pass” (or however it’s spelled) inside displays (and “tuple displays”) and argument lists—might be worth it, even if the fully general one isn’t. That being said, I still haven’t seen an example of a function call or list display that actually looks compelling. I think if anyone wants to convince everyone that some form of this proposal is worthwhile, that’s the first thing they need to provide.

I'm concerned that in this thread, and elsewhere, we're not paying sufficient attention to https://en.wikipedia.org/wiki/Cognitive_load. Adding a feature to a system can increase the cognitive load in learning the system, and it can also reduce the cognitive load in using the system. Sometimes adding a feature can reduce cognitive load in both learning and using the system, but I suspect that is exceptional. I suggest that in this thread we have more discussion of the cognitive load adding this feature would add to those learning the system. I also suggest, as someone else has already done, that we focus more on the problem to be solved rather than a specific solution. -- Jonathan

On 24/06/2019 20:50, Jonathan Fine wrote:
I'm concerned that in this thread, and elsewhere, we're not paying sufficient attention to https://en.wikipedia.org/wiki/Cognitive_load.
I was under the impression that most of the counter-arguments could be rephrased as "this increases cognitive load for no clear benefit" myself. A sentiment I agree with, by the way. -- Rhodri James *-* Kynesim Ltd

On Mon, Jun 03, 2019 at 08:57:22PM -0400, James Lu wrote:
Then the "unless" clause is superfluorous and we can write: if condition1 and not condition2 expression which is another way of saying if condition expression which has been suggested before in the form: expression if condition and rejected. Please check the archives.
Which is precisely how "and" already works.
I don't even know what you mean by that even after reading your examples, sorry. If I do this: var = if False 1 unless False print(var) what happens? -- Steven

On 6/3/2019 8:57 PM, James Lu wrote:
An expression MUST evaluate to a value. EMPTY is spelled None in Python. No need for an alternate spelling. The only place that None is implicit rather than explicit is the implicit 'return None' *statement* at the end of functions. That is enough. -- Terry Jan Reedy
participants (12)
-
Andrew Barnert
-
Chris Angelico
-
Dan Sommers
-
Greg Ewing
-
James Lu
-
Jonathan Fine
-
MRAB
-
Rhodri James
-
Robert Vanden Eynde
-
Stephen J. Turnbull
-
Steven D'Aprano
-
Terry Reedy