Conditional collection literals

Both of those would be illegal. This would be a bit of a special case, yes, similar to how star-unpacking expression are sometimes allowed in annotations.

Then that’s a non-starter, I’m afraid. The two parts of an if/else expression have to themselves be expressions, not magical syntactic constructs, and we’re right back where we started.

1 Like

… Why? Are there any implementation issues you can think of? Are there any explanation issues[1] you can think of?

Or is it just that you want the language to be perfectly, unambiguously orthogonal - because that ship has already sailed with a variety of features?


  1. I.e. are there any ways people would be able to get completely confused by this in a way that can’t be explained to them. ↩︎

Okay. What do these mean?

[1 if (*x if cond else x) else 2]
[*x if cond else y if cond else *z]

etc. How do you interpret these? Or is it just a magical notation?

Actually, you know what: Go ahead, implement it, then see if people like it. I don’t think it’s worth discussing in theory.

Both of these have clear and IMO obvious resolutions. The first one is invalid since there is no container to unpack the value of the inner *x into (otoh, [1 if (*x if cond else x,) else 2] would be valid which is a bit of a downside). The latter is valid an either produces [*x], [y] or [*z].

The grammar based rule I would try to implement first (not sure if that would run into any issues, haven’t carefully checked yet is to make star_named_expression including star_if_else:

star_if_else: star_expression 'if' disjunction 'else' star_expression

(this probably requires careful reordering of other stuff to prevent too much recursion, but I don’t think it should be difficult)

I.e. everywhere you could previously write a *expression as an expression [1] you can now also write *expression if expression else *expression.

For explaining this simply: The entire if-else-expression gets replaced with the picked branch and then the star gets applied as normal. If star unpacking wasn’t allowed before at this location, it isn’t allowed now.

How else would you interpret your examples? This is a genuine question; If there is some ambiguity I am overlooking here I would love to know it. Or is it just that it’s new syntax?

You can read it like English as well

The value of [*x if cond else y if cond else *z] is the elements of x if cond is true else y if cond else the elements of z. [2]


  1. i.e. not including function parameters and not including patterns in match-case statements ↩︎

  2. Ofcourse this kind of nested if-else is unnatural and ambiguous in normal english. But that isn’t new with this idea. ↩︎

Seems to me the “if” without an “else” wouldn’t be so bad, because we
already have that in comprehensions. So we would be partially unifying
displays and comprehensions by allowing it in both.

If we wanted, I think we could go even further and fully unify them by
allowing any element of a display to be a comprehension. Then a
conditional element would just be a special case of a comprehension element.

2 Likes

May I add my -1 here? “Simple is better than complex” and one of the easiest way how to include this unwanted complexity is by inserting yet another unnecessary micro-sublanguage to Python. We have already too many of them (starting with re, but that’s probably inevitable, and I am already mildly unhappy with list comprehensions, because they tend to be abused in horrible ways as contenders in the Obfuscated Python Code Contest). Please, don’t! Simple question before you suggest any change in the Python language: “how difficult it would be to write a simple function which would achieve the desired effect?”. If the answer is (as it is here) “not difficult at all”, then I give -1 to the idea.

2 Likes

Btw the best answer is this one (issue solved with one lambda).

include = lambda cond, *args : [*args] if cond else []

[1, *include(False, 2), *include(True, 3, 4)]

And another lambda for dicts

kwinclude = lambda cond, **kwargs : {**kwargs} if cond else {}

dict(a=1, **kwinclude(False, b=2), **kwinclude(True, c=3, d=4))
4 Likes

Those might be worth proposing to more-itertools, but FYI kwinclude only supports string keys.

Meh, everything that the include lambda archives, can be done better with alternatives:

 a=[1,2,*((3,4) if cond else ())]

for example evaluates the true case only if cond is true.

Did you read the OP? That’s exactly what this issue is trying to simplify.

It’s interesting that you would think a lambda is the neatest solution, rather than

def include(cond, *args): return args if cond else []
def kwinclude(cond, **kwargs): return kwargs if cond else {}

assert [1, *include(False, 2), *include(True, 3, 4)] == [1,3,4]
assert dict(a=1, **kwinclude(False, b=2), **kwinclude(True, c=3, d=4)) == dict(a=1, c=3, d=4)

is a 1-line lambda “allowed” whereas a 1-line function def is not? If it suddenly looks too complicated if you format it as

def include(cond, *args) -> tuple:
  "docstring"
  return args if cond else ()

def kwinclude(cond, d:dict = {}, /, **kwargs) -> dict:
  "docstring"
  return d|kwargs if cond else {}

assert [1, *include(False, 2), *include(True, 3, 4)] == [1,3,4]
assert dict(a=1, **kwinclude(False, b=2), **kwinclude(True, c=3, d=4)) == dict(a=1, c=3, d=4)

where was the simplicity really coming from?

You would probably better use def if you use *include globally because it emphasizes its existence. But if you use it locally within one already nested function, lambda is less disruptive for readability.

Of course I did and I have some sympaties for the op’s proposal.

As opposed to the op’s proposal and currently used idioms, the proposal of hprodh has only downsides!!

Ah, of course, so what you’re saying is that the status quo is better than defining functions? I agree with that.

1 Like

I am very thankful for all your feedback. I am currently thinking about all the things written (and will continue to do so) and come back with a clearer vision on this topic. You brought up a wide range of possible solutions I couldn’t have thought about all by myself. Thanks for taking the time :slight_smile:

I want to summarize a few points in my head right now:

  1. Yes, there are idioms. I didn’t know all of them to be honest. But I also do not think that they are beautiful.
  2. Python lacks a real notion of “nothing”, whatever *() maybe would be. None is not enough nothing for this use case. If we had one, we could just use the if-else syntax. A custom sentinel value was proposed, but then you would have to filter the collection after constructing it which hits the performance and is an idiom in itself. A new sentinel which the interpreter could magically handle by itself may be an answer. I like the proposal of pass right now, but haven’t thought it through yet. Overall, we either need something to express “nothing” or write code to remove something to achieve “nothing”.
  3. As I see it, using this new syntax in function calls is complicated enough on its own. That’s why I excluded it in my post. But I see the connection and the use for triggering default values.
  4. There seems to be general disagreement about what “beautiful python” is. I am not surprised by this, but I may be worth reflecting when we discuss about a syntax being “more beautiful”. I like my proposel because it looks like if-else. For me, its just the logical consequence of not providing an alternative value. But I also see that some people do not like this. I also like the functional style that comes with it. But some people like to think purely in statements. Or in statement-like expression syntax, which I can understand but is a little to wild for my taste to be honest.
12 Likes