1

I was reading some code and I noticed that this code has many of these lines:

 try: 
   rv = somefunction()
 except SomeException, e:
   rv = 0

 if rv == 0:
    doSomething() ...

It seemed to me that using this style of comparison operations is bound to be slower than:

try: 
   rv = somefunction()
 except SomeException, e:
   rv = None

 if is not None:
    doSomething() 
    ...
oz123
  • 27,559
  • 27
  • 125
  • 187
  • As @zybjtu just pointed out under my answer, if `doSomething()` should be called if `somefunction()` returns `0`, your suggested changes break the code. – jonrsharpe Jan 09 '14 at 13:48
  • @MartijnPieters, as usual: sharp eyes! fixed. – oz123 Jan 09 '14 at 13:49
  • 2
    Say `==0` costs 5 cents, `is None` costs 2 cents and `except` costs 1000 dollars. Is $1000.02 really cheaper than $1000.05? – georg Jan 09 '14 at 13:52
  • one thing @jonrsharpe brought up is that there is a semantic difference here... are either 0 or None valid output values for the first function? – Corley Brigman Jan 09 '14 at 13:57

3 Answers3

2

Would it be more efficient to just do:

 try: 
     rv = somefunction()
 except SomeException, e:
     doSomething() ...

You may still need to set rv = 0 if an integer value is needed elsewhere, which is a limiting factor on your examples above.

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
1

Maybe it depends on whether you are in python 2 or python 3. In python 2, the only 'slow' operation is `== None'. I thought it was because integers in low ranges were precreated objects, but in actuality that didn't matter.

In [53]: x = 0

In [54]: %timeit x is 0
10000000 loops, best of 3: 38 ns per loop

In [55]: %timeit x == 0
10000000 loops, best of 3: 36.5 ns per loop

In [56]: x = 1

In [57]: %timeit x is 0
10000000 loops, best of 3: 37.3 ns per loop

In [58]: %timeit x == 0
10000000 loops, best of 3: 36.5 ns per loop

In [59]: %timeit x is None
10000000 loops, best of 3: 38.1 ns per loop

In [60]: %timeit x == None
10000000 loops, best of 3: 82.9 ns per loop

Now, in the first 3 cases, x is an int, which is the same type in the first 3 cases, and not in the last. So tried one additional thing, making x a string, so it would not match type with 0:

In [62]: x = 'x'

In [63]: %timeit x is 0
10000000 loops, best of 3: 38.7 ns per loop

In [64]: %timeit x == 0
10000000 loops, best of 3: 92.5 ns per loop

In [65]: %timeit x is None
10000000 loops, best of 3: 39.1 ns per loop

In [66]: %timeit x == None
10000000 loops, best of 3: 77.1 ns per loop

In this case, the 'is' is still pretty fast, no matter what the types, while mixed type equality checking is slower. This makes sense, as 'is' is just an equality check on the id, while equals can be arbitrarily slow.

Meaning that if you really concerned about this (which you probably don't need to be as mentioned, unless this is in an inner loop), you should create a sentinel object (of any time), and then use the is operator to check against it directly. None is a convenient one as long as it's not a valid value; 0 or -1 can fulfill the same role. But it doesn't have to be those.

Corley Brigman
  • 11,633
  • 5
  • 33
  • 40
0

To show my mate this is possibly true, I did some benchmarking with None, 0 and False:

I have read before this question, When is the `==` operator not equivalent to the `is` operator? (Python). And I even added some benchmarking to try and convince my mates that this could be optimized:

In [92]: def TestInt():
   ....:     i = 0
   ....:     if i:
   ....:         pass
   ....:     

In [93]: def TestBool():
   ....:     i = False
   ....:     if i:
   ....:         pass
   ....:     

In [95]: def TestNone():
   ....:     i = None
   ....:     if i:
   ....:         pass
   ....:     


In [97]: timeit.timeit(TestInt, number=10**5)
Out[97]: 0.01352691650390625

In [98]: timeit.timeit(TestBool, number=10**5)
Out[98]: 0.014671087265014648

In [99]: timeit.timeit(TestNone, number=10**5)
Out[99]: 0.009851932525634766

Obviously comparing if an object is not None is always a little bit faster!

Community
  • 1
  • 1
oz123
  • 27,559
  • 27
  • 125
  • 187
  • Those tests are meaningless. Notice that `if i` is not at all the same as `if i == 0`, nor the same as `if i is not None`. All emit subtly different code that might change your benchmark results completely. – Konrad Rudolph Jan 09 '14 at 13:33
  • is that really worth it? – zhangxaochen Jan 09 '14 at 13:33
  • 2
    Yes, testing for `None` is faster because Python can make it a constant, secure in the knowledge that `None = 0` is not allowed. In Python 3 `True` and `False` are now keywords and the same applies to those. – Martijn Pieters Jan 09 '14 at 13:34
  • @MartijnPieters Actually testing for `None` is slower than `False`, but loading `None` is faster, and the loading time dominates in this particular benchmark. – augurar Nov 13 '16 at 08:13
  • @augurar: the benchmark is next to meaningless as even a million tests take only a 10th of a second. This is micro-optimisation at its worst. Note that this is not a `is not None` test, but a truth test; it is to be expected that the first thing Python tests for is `True` / `False`, before applying other tests. And that's [exactly what happens](https://hg.python.org/cpython/file/3.5/Python/ceval.c#l2867) (testing true, then false, then object truth). – Martijn Pieters Nov 13 '16 at 16:25
  • @augurar: but yes, in Python 2 the `TestBool()` test is slower because the interpreter can't just load a constant, a global lookup has to be executed first. Python 3 eliminates that because `False` is now a keyword and thus a constant. A better test would be to pass in `i` as an argument so that you are only testing the `if` check. – Martijn Pieters Nov 13 '16 at 16:28