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.
⌠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?
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]
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.
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.
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))
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.
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
I want to summarize a few points in my head right now:
- Yes, there are idioms. I didnât know all of them to be honest. But I also do not think that they are beautiful.
- 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 ofpass
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â. - 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.
- 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.