Is it pythonic to use or
, similar to how PHP would use or die()
?
I have been using
quiet or print(stuff)
instead of
if verbose:
print(stuff)
lately.
I think it looks nicer, they do the same thing, and it saves on a line. Would one be better than the other in terms of performance?
The bytecode for both look pretty much the same to me, but I don't really know what I'm looking at...
or
2 0 LOAD_FAST 0 (quiet)
3 JUMP_IF_TRUE_OR_POP 15
6 LOAD_GLOBAL 0 (print)
9 LOAD_CONST 1 ('foo')
12 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
>> 15 POP_TOP
16 LOAD_CONST 0 (None)
19 RETURN_VALUE
vs if
2 0 LOAD_FAST 0 (verbose)
3 POP_JUMP_IF_FALSE 19
3 6 LOAD_GLOBAL 0 (print)
9 LOAD_CONST 1 ('bar')
12 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
15 POP_TOP
16 JUMP_FORWARD 0 (to 19)
>> 19 LOAD_CONST 0 (None)
22 RETURN_VALUE
No, this is definitely not Pythonic. Many of the decision made along the way have been specifically to discourage this kind of coding.
The right way to write this is the obvious way:
if verbose:
print(stuff)
Or, if computing stuff
in itself isn't expensive/dangerous/irrevocable, just wrap print
in a function that checks the flag:
def printv(*args, **kwargs):
if verbose:
print(*args, **kwargs)
… so you can just do this, which is about as concise as you're going to get:
printv(stuff)
Or, even better, use the logging
module instead of reinventing the wheel.
But what if stuff
is expensive, and you really want to save a line?
Well, you probably don't really need to save a line, and doing so is always sacrificing readability for brevity; "Readability Counts" is a key part of the Zen of Python. But Python does allow you to put a one-line suite on the same line as the condition:
if verbose: print(stuff)
As PEP 8 puts it, "sometimes it's okay", but it's "generally discouraged". As Guido put it in an email on the ideas mailing list, "If you must save a line, use a one-line if statement rather than something 'clever'. But if you must save a line, I probably don't want to read your code."
If you want this in an expression (which you shouldn't, as I'll explain below), or you want to technically follow PEP 8 to the letter while blatantly violating the spirit:
print(stuff) if verbose else None
So, what's not Pythonic about it?
For one thing, you're using the result of print
in an expression, even though print
has no useful result, and is only called for its side effects. This is misleading.
And in general, Python has only one side effect per line, and it happens as far to the left as possible, which makes it easy to skim code and see what's changing. This is reinforced by the strong divide between statements and expressions (and, e.g., assignments being statements), by methods with side effects idiomatically returning None
instead of self
, and so on.
But even ignoring all that, using and
and or
just for short-circuiting, rather than for their results, is potentially confusing and, at least in some people's minds, ugly. That's why the ternary expression (spam if eggs else beans
) was added: to stop people from writing eggs and spam or beans
. Why is it confusing? For one thing, it doesn't really read like English at all, unless you've been reading more Perl code than actual English. For another, it's very easy to accidentally use a valid value that happens to be falsey; you know not to do that, and to check for that, when you see an if
, but you don't here. Also note that the print(stuff) if verbose else None
makes it explicit that you are creating a value which you then do nothing with; explicit is always better than implicit, but especially so when you're doing something uncommon.
Finally, as for performance: (a) who cares, and (b) why not measure it instead of try to guess by reading bytecode you don't understand?
In [511]: quiet = True
In [512]: %timeit quiet or None
10000000 loops, best of 3: 48.2 ns per loop
In [513]: verbose=False
In [514]: %timeit if verbose: pass
10000000 loops, best of 3: 38.5 ns per loop
So there you go, in the fast-pass case, the if
statement is actually about 20% faster, not slower—and they're both so fast that it's unlikely to ever affect your program anyway. If you're doing this a billion times in a tight loop and need to squeeze out that performance, you're going to want to lift the rest out of the loop, even if that means repeating yourself with two near-clones of the same code (especially considering that a loop without the print
s is more likely to fit into cache, etc.).
If you want to know why, well, you'd have to look at the implementation of those bytecodes on the particular version of the particular implementation that you care about… but most likely, needing to do an extra POP_TOP
instead of having one merged into the previous operation is part of the difference.
I don't think this is pythonic. ("Explicit is better than implicit").
You can write
if verbose: print(stuff)
so if you desperately need to keep your line count down, you can.
The most pythonic way ("Readability counts.") would still be to use
if verbose:
print(stuff)