4 ms·
> Anyway, this is just a style thing. It doesn’t affect program correctness or structure in a meaningful way. The nice thing about ternaries is that they're ex
by chriswarbo 5y ago
> Anyway, this is just a style thing. It doesn’t affect program correctness or structure in a meaningful way.
The nice thing about ternaries is that they're expressions, so they don't "infect" our code like statements (e.g. 'if'). For example:
if xCond:
x = x1
else:
x = x2
if yCond:
y = y1
else:
y = y2
if zCond:
z = z1
else:
z = z2
foo(x, y, z)
This lint rule will tell us to do the following instead:
x = x1 if xCond else x2
y = y1 if yCond else y2
z = z1 if zCond else z2
foo(x, y, z)
Now that our branching is done with expressions rather than statements, we can actually in-line them, which seems much nicer (less chance to get them mixed up; no need to invent new names; no side-effects, e.g. accidentally overwriting an existing variable; etc.)
foo(
x1 if xCond else x2,
y1 if yCond else y2,
z1 if zCond else z2,
)
If we really want the names, we could define them using the 'walrus operator':
foo(
x := (x1 if xCond else x2),
y := (y1 if yCond else y2),
z := (z1 if zCond else z2),
)
Personally I would use separate 'x = ...' statements rather than :=, since we want to explicitly perform effects (binding names).
- joshuamorton 5y agoYou don't need the walrus operator, as keyword args are valid. f(x=a if b else c) Should be valid. All the walrus gets you is making the x "infect" the surrounding namespace which is surprising.
- hermitdev 5y agoExcept it's different semantics. The example with 'foo' and the walrus operator doesn't even use keyword arguments, and the keyword arguments may not even be valid, depending on the definition of 'foo'.
- chriswarbo 5y ago> All the walrus gets you is making the x "infect" the surrounding namespace which is surprising. Yes, that's what I intended. I was pointing out that the foo(x1 if ...) version does not behave the same as the separate 'x = ...' statements, since it doesn't bind the x/y/z variables. This is usually an advantage (if we don't need those values for anything else), but for completeness I noted that we could use the walrus to recreate the exact behaviour (keyword arguments can't do it, since they only bind variables inside the function call, which (usually) has no observable difference to using positional arguments). You're right that the walrus's effect on the surrounding namespace is 'surprising', and that's why I would prefer to use separate statements if we really want those names defined (since that's less surprising).
- jackdh 5y agoI think I might be in the minority here that I prefer the fully laid out statements. Sure in small cases this small ternary use is great! However too many times I've seen people chain them for far too many characters just to be "on one line". We don't use one letter variable names anymore, so in the same reasoning why should we do the same to our code?
- Cthulhu_ 5y agoYup; the booleans are difficult enough from a logic point of view, don't need to make it more complicated by using syntactic sugar. I strongly disagree with the author's idea that shorter is better. Clarity trumps conciseness. I'll admit that not enough conciseness can impact clarity, but there's ways and means to clean that up that don't involve clever code.
- chriswarbo 5y agoI hypothesise that the actual meaning (possibly subconscious) underlying those uses of "complicated", "syntactic sugar", and "clever code" is 'not the way I'd write it'; and that "clarity" and "clean" actually mean 'the way I would write it'. To test whether those statements are guiding principles, or merely ad-hoc justifications, I propose the following pylint rules based on those principles: no-ternary-sugar: Replace 'x if y else z' with '{True: lambda: x, False: lambda: z}[y]()'. This is less concise, but improves clarity by using booleans explicitly, and making the delayed evaluation of the results clear; both of which are implicit in the 'if/else' syntactic sugar. no-elif-sugar: Replace: if foo: bar elif baz: quux ... else: foobar With: if foo: bar else: if baz: quux else: ... else: foobar This is less concise, but makes the branching structure clear, unlike the misleading "flat" appearance of the 'elif' syntactic sugar. no-special-case-patterns: Replace: if foo: bar else: baz With: match foo: case True: bar case False: baz 'if/else' is syntactic sugar for pattern-matching a boolean, which is left implicit. Explicit matching improves consistency with other use-cases, and makes the relationship between branches and boolean clear, at the expense of conciseness. (Also applies to single-armed 'if', which will only have a 'case True:'). Of course, the combination of no-elif-sugar and no-special-case-patterns would give extreme clarity like this: match foo: case True: bar case False: match baz: case True: quux case False: ... case False: foobar