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"
Rubens
  • 14,478
  • 11
  • 63
  • 92

3 Answers3

52

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")
abarnert
  • 354,177
  • 51
  • 601
  • 671
  • 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. – Rubens Dec 31 '12 at 20:07
  • Returning `None` is the same as returning `null`, and therefore [just as bad](http://en.wikipedia.org/wiki/Void_safety). –  Dec 31 '12 at 20:24
  • 1
    @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. – abarnert Dec 31 '12 at 20:25
  • @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?) – abarnert Dec 31 '12 at 20:29
  • @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. –  Dec 31 '12 at 20:34
  • @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)`. – abarnert Dec 31 '12 at 20:48
  • Nice - maybe (_instead of return None ... just return defvalue_) -> so `def findNext(self, needle, defvalue=u'DEFVALUE'):` – Mr_and_Mrs_D Mar 16 '14 at 17:23
  • @abarnert: could you point me to discussions of why `value = info.findNext("b") or "Oompa Loompa"` is discouraged? – user3067927 Nov 09 '17 at 16:34
22

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

value = info.findNext("b") or "Oompa Loompa"
Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
  • 1
    It is [certainly a shortcut](http://docs.python.org/2/library/stdtypes.html#boolean-operations-and-or-not), @Rubens. `x or y` should compile into the same code as `if bool(x) then x else y`. `bool` calls `.__nonzero__()`. –  Dec 31 '12 at 19:51
  • 1
    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. – abarnert Dec 31 '12 at 20:03
  • 1
    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. – Jonathan Dec 10 '20 at 14:49
1

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"