Compressing `x if x else y` statement in Python
Asked Answered
W

3

44

I'm quite acquainted with Python's ternary operator approach:

value = foo if something else bar

My question is very simple: without prior assignments, is there anyway to reference the term being evaluated in (if ...) from one of the return operands (... if or else ...)?

The motivation here is that sometimes I use expressions in if ... that are exactly what I'd like to have as result in the ternary operation; happens though that, for small expressions, there's no problem repeating it, but for a bit longer expressions, it goes somewhat nasty. Take this as an example:

value = info.findNext("b") if info.findNext("b") else "Oompa Loompa"
While answered 31/12, 2012 at 19:44 Comment(8)
well, Oompa Loompa is funny!Tabethatabib
@AspiringAqib we don't have the assignment, but we can still enjoy some oompa loompas (:While
well,you got solution down there huh?Tabethatabib
@AspiringAqib yep, I guess this is more than enough, and may even the best solution (: better keep on KISS'in things (:While
Why doesn't findNext take an optional default value, like get and the hundreds of other methods modeled after it? Then you don't need this at all.Capsular
@Capsular findNext is not a function I wrote; this is from BeautifulSoup parser API.While
@Rubens: Then write a two-line wrapper around it, and use that.Capsular
@Capsular I'd be writing way too many two-line wrappers here ^^ I'm fine with the coalescing operators.While
C
54

There is no way to do this, and that's intentional. The ternary if is only supposed to be used for trivial cases.

If you want to use the result of a computation twice, put it in a temporary variable:

value = info.findNext("b")
value = value if value else "Oompa Loompa"

Once you do this, it becomes clear that you're doing something silly, and in fact the pythonic way to write this is:

value = info.findNext("b")
if not value:
    value = "Oompa Loompa"

And that's actually 5 fewer keystrokes than your original attempt.

If you really want to save keystrokes, you can instead do this:

value = info.findNext("b") or "Oompa Loompa"

But that's discouraged by many style guides, and by the BDFL.

If you're only doing this once, it's better to be more explicit. If you're doing it half a dozen times, it's trivial—and much better—to make findNext take an optional default to return instead of None, just like all those built-in and stdlib functions:

def findNext(self, needle, defvalue=None):
    # same code as before, but instead of return None or falling off the end,
    # just return defvalue.

Then you can do this:

value = info.findNext("b", "Oompa Loompa")
Capsular answered 31/12, 2012 at 20:1 Comment(8)
I actually have some curious tics with programming: i don't like writing more than 70 columns, and I really appreciate one-line solutions; the problem here is rather about compactness than explicit number of words. I'm accepting your answer, though, as you're answering a yes/no to my binary question. Agreeing, nonetheless, that using or, as pointed by @Ignacio is quite the best solution after all.While
Returning None is the same as returning null, and therefore just as bad.Sipper
@Rubens: The pythonic way to write 1-line solutions is to wrap up anything complicated in an explicit function. If you can add a defval to findNext, do so; if you can't, write a wrapper. Different languages have different idiomatic styles (e.g., in Haskell, naming a function you're only going to call once is code smell), and different languages adhere to their idioms more strongly (e.g., in perl, "There's only one way to do it" would be considered a bug, not a feature). But if you've chosen Python, you should learn idiomatic Python.Capsular
@Tinctorius: The None is there in the example because I assume it's what the OP's original findNext returns—just like dict.get and all of the other methods on the same model. It obviously returns something falsey, and no real result can be falsey, or the OP's code is just wrong, and I'm assuming that it isn't. At any rate, this is all irrelevant to the OP's problem. (But, as a side note: do you think Haskell's Nothing violates void safety? If not, what's the difference between Nothing and None beyond static vs. dynamic typing?)Capsular
@abarnert: If you have dynamic typing, then I strongly prefer the "default return value" approach. Throwing a KeyError is fine too, but people might find that that clutters the code. I don't think Haskell's Maybe violates void safety at all. A void-insecure language makes many types nullable by default. Maybe allows you to opt in into nullability. The only way Haskell is void unsafe is by its bottom values (i.e. hard-to-catch termination like undefined, or non-termination like fix id), but to fix that, your language would become harder to use and harder to implement.Sipper
@Tinctorius: I'm not sure which approach you mean by "default return value", but if it's what I think it is—namely, that any function that can return some default value lets the caller pass that default value instead of forcing one—then I agree completely, and it's exactly what I suggested to the OP. And it's the standard Python idiom—when d[key] with its KeyError is inappropriate, the alternative is d.get(key, defval=None), not just d.get(key).Capsular
Nice - maybe (instead of return None ... just return defvalue) -> so def findNext(self, needle, defvalue=u'DEFVALUE'):Interfile
@abarnert: could you point me to discussions of why value = info.findNext("b") or "Oompa Loompa" is discouraged?Acescent
O
24

Don't use if ... else at all. Instead, take advantage of Python's coalescing operators.

value = info.findNext("b") or "Oompa Loompa"
Overflow answered 31/12, 2012 at 19:46 Comment(3)
It is certainly a shortcut, @Rubens. x or y should compile into the same code as if bool(x) then x else y. bool calls .__nonzero__().Sipper
You can do this, but that doesn't mean you should. If you're doing this enough times that saving 5 keystrokes matters, modify the findNext method to take a default value. Otherwise, write it explicitly.Capsular
This kind of thing can burn you if there's anything falsy thrown into the mix. Consider [] or 'default' - this might be what you're looking for, it might not.Grueling
P
3

If you have python 3.8 or greater, you can use the := operator. In your example, it would look like

value = bb if (bb := info.findNext("b")) else "Oompa Loompa"
Platelet answered 8/6, 2022 at 20:9 Comment(2)
Actually walrus operator introduced in Python 3.8Winni
you forgot to put : before the = sign.Winni

© 2022 - 2024 — McMap. All rights reserved.