views:

192

answers:

6

I have to extract values from a variable that may be None, with some defaults in mind. I first wrote this code:

if self.maxTiles is None:
    maxX, maxY = 2, 2
else:
    maxX, maxY = self.maxTiles

Then I realized I could shorten it to:

maxX, maxY = self.maxTiles if self.maxTiles is not None else (2, 2)

But then I realized this might be the most succinct and easily readable:

maxX, maxY = self.maxTiles or (2, 2)

Is the latter acceptable, or too hackish?

+4  A: 

Along with the answer of gddc ( of the problems of assuming maxTiles is a tuple), I would probably do the second option, but add parenthesis for clarity:

maxX, maxY = (self.maxTiles) if (self.maxTiles is not None) else (2, 2)
Stargazer712
I prefer this one. It is a natural way of expressing defaults.
Muhammad Alkarouri
A: 

I don't like using or and and as replacements for a ternary operator in Python. I've run into problems like a 0 value being treated as "false" too many times, when I only intended to check for None. I consider it much better to be explicit, even if its more verbose, so your second example is best:

maxX, maxY = self.maxTiles if self.maxTiles is not None else (2, 2)
Evgeny
+4  A: 

If you're doing this at the beginning of a function, I'd use the longer form as it's more idiomatic and instantly recognizable. Yeah, it's more lines, but you barely save any characters, and short lines that fit into 79 character lines = good.

Plus if you ever have to adjust the logic or add more steps you'd likely revert to the long form anyways.

Nick T
+1 for considering maintainability. The chances are that the person maintaining the code will not be the author!
Johnsyweb
+3  A: 

I avoid the y if x else z syntax when I can. It's inherently an ugly, unintuitive syntax, and one of the bigger mistakes in Python's design. It's an out-of-order expression: x is evaluated before y. It's unintuitive; it's naturally read as "if x then y, else z". C's syntax gives us a decades-established, universally-understood order for this: x? y:z. Python got this one very wrong.

That said, ternary syntax is the wrong mechanism for supplying a default anyway. In self.maxTiles if self.maxTiles is not None else (2, 2), note the redundancy: you have to specify self.maxTiles twice. That's repetitive, so it takes more work to read the code. I have to read it twice to be sure it doesn't say, for example, self.minTiles if self.maxTiles is not None else (2, 2).

self.maxTiles or (0,2) avoids these problems; it's perfectly clear at a glance.

One caveat: if self.maxTiles is () or 0 or some other false value, the result is different. This is probably acceptable based on what you seem to be doing, but keep this in mind. It's an issue when providing a default for a boolean or an integer, and you really do need the is None test. For those I prefer a simple conditional, but will sometimes fall back on a ternary expression.

Edit; a clearer way of writing the conditional version is:

if self.maxTiles is None:
    maxX, maxY = 2, 2
else:
    maxX, maxY = self.maxTiles
Glenn Maynard
+1  A: 

You code is perfectly acceptable idiom. In fact I find it more readable than the first two.

My only consideration is you are doing two things in one line, to supply a default and to unpack them into x,y. It maybe more clear if you separate them into two.

maxTiles = self.maxTiles or (2, 2)
maxX, maxY = maxTiles

This also deflect the criticism of g.d.d.c, although it is not really a serious one.

Wai Yip Tung
+6  A: 

About, specifically,

self.maxTiles if self.maxTiles is not None else (2, 2)

I've found that "double negatives" of the general form if not A: B else: C (whether as statements or expressions) can be quite confusing / misleading; this isn't literally an if not .. else, but moving the not doesn't make the "double negative" go away.

So, in general, I just rewrite such constructs to if A: C else: B. In this particular case, if I did choose the ternary-operator form, I'd code it as

(2, 2) if self.maxTiles is None else self.maxTiles

On to the more general question: a = b or c is fine if and only if you really want to use c for any false value of b -- it's not fine to deal specifically with b being None. IOW, b or c is a better way to express

b if b else c

but it's not a way to express a similar expression where the core test is, instead, b is None. In theory, if you "know" that the only possible false value for b is None, they're semantically equivalent, but that strong "only possible false value" constraint will not be apparent to readers / maintainers of your code -- and if you have to add a comment to explain that, any conciseness advantages that or might claim are nullified... better, when feasible, to "say it in code", rather than have the code be obscure and need comments to clarify exactly what it's doing and when (comments that are really useful are rather those which explain, not the what and the when [[the code itself should show that!-)]], but rather the why when it's not obvious -- what's the application purpose being served by this specific tidbit of code functionality).

Alex Martelli
+1 - let's promote this as the state-of-the-Python idiom for default args: `self.var = default_value if arg is None else arg`. Minimal repetition, clear indication that passing "None" means "use the default value," and no risk of accidental boolean evaluation of values that confuse clever and/or expressions.
Paul McGuire
We should stop avoiding not using double negatives any more!
Paul McGuire
@Paul, wrt your 1st comment, that's fine if `None` is definitely known _not_ an acceptable value. Otherwise, a `_sentinel = object()` before the `def` and usually in the same scope, to use in the `def` (with `arg=_sentinel` instead of `arg=None`), then using `if arg is _sentinel` instead of `is None`, is a sounder approach. Apart from that, for code that doesn't need to avoid the ternary op (to run on old Python versions, or to be _extremely_ beginner-oriented), it's fine (otherwise, I'd recommend a multi-statement approach).
Alex Martelli
@Paul: the main thing w/ doing that is that if the usual case is to not use the default value, it's strange cause it's all the way on the right
Claudiu
@Claudiu: in `self.var = arg`, the arg is still all the way to the right. The computer doesn't care how far to the right it has to go for the usual case - from an information point of view, having the default value left-most makes it clear just what the default value is (as well as a hint on the expected type and form of the incoming arg - who would have guessed in this example that `maxTiles` was supposed to be a 2-integer tuple?).
Paul McGuire
I'm a huge believer in optimizing for clarity over worrying about microbenchmarks. Still, in a tight inner loop, it seems like `value or default` would be the fastest, and that `default if value is None else value` may or may not be slower than `value if value is not None else default`, depending on how often `value is None` is True, due to shortcut evaluation.
Just Some Guy
another thing to think about is, from a functional programming point of view, "value or default" (and the if things) have a lot less mutation going on, and mutation can lead to problems. there's lots of potential error in the first approach - more characters typed, more room to make mistakes, more chances the if else: don't assign the same variables, etc. from a "what's going on" point of view, "or" idiom is much simpler.
Claudiu
@Claudiu, the optimal idioms in a purely-functional (or nearly-so) programming language, vs a multi-paradigm language with OOP at its core, are obviously drastically different, to the point it would be absurd to judge either by the other's standards. Name rebinding is obviously a perfectly fine idiom in a language that's designed exactly around such constructs, just as (say) tail recursion just is great in languages designed to optimally support THAT. You're just applying inappropriate criteria, when judging name rebinding to be a "mutation [which] can lead to problems" **in Python**.
Alex Martelli