17

Say I have a function in Python that uses a constant computed float value like 1/3.

def div_by_3(x):
    return x * (1/3)

If I call the function repeatedly, will the value of 1/3 be automatically cached for efficiency? Or do I have to do something manually such as the following?

def div_by_3(x, _ONE_THIRD=1/3):
    return x * _ONE_THIRD
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
aiai
  • 525
  • 4
  • 11
  • 1
    The latter, although you could keep the constant as a module level global variable instead of passing it the the function (unless you want to pass it as a parameter). – Alexander Oct 07 '15 at 19:11
  • I think doing it as a parameter is faster (avoids looking up the global name). – aiai Oct 07 '15 at 19:25
  • Nope. No difference in speed, and less clear to the user of the function if it is not a parameter. – Alexander Oct 07 '15 at 19:32

1 Answers1

16

Find out for yourself! The dis module is great for inspecting this sort of stuff:

>>> from dis import dis
>>> def div_by_3(x):
...     return x * (1/3.)
... 
>>> dis(div_by_3)
  2           0 LOAD_FAST                0 (x)
              3 LOAD_CONST               1 (1)
              6 LOAD_CONST               2 (3.0)
              9 BINARY_DIVIDE       
             10 BINARY_MULTIPLY     
             11 RETURN_VALUE        

As you can see, the 1/3 calculation happens every time. (Note: I changed 3 to 3. to force float division, otherwise it'd just be 0. You can also enable future-division, which actually changed the behavior, see edit section below).

And your second approach:

>>> def db3(x, _ONE_THIRD=1/3.):
...   return x * _ONE_THIRD
... 
>>> dis(db3)
  2           0 LOAD_FAST                0 (x)
              3 LOAD_FAST                1 (_ONE_THIRD)
              6 BINARY_MULTIPLY     
              7 RETURN_VALUE        

More information on the second can be found by inspecting the function object:

>>> inspect.getargspec(db3)
ArgSpec(args=['x', '_ONE_THIRD'], varargs=None, keywords=None, defaults=(0.3333333333333333,))

You can see the default value is cached in there.

EDIT: Turns out this is a little more interesting -- in Python 3 they do get cached (and also in Python 2.7 when you enable from __future__ import division):

>>> dis.dis(div_by_3)
  2           0 LOAD_FAST                0 (x)
              3 LOAD_CONST               3 (0.3333333333333333)
              6 BINARY_MULTIPLY
              7 RETURN_VALUE

Switching to integer division (//) in either Python 3 or 2.7-with-future-division doesn't change this, it just alters the constant to be a 0 instead of 0.333.. Also, using integer division directly in 2.7 without future-division will cache the 0 as well.

Learned something new today!

tzaman
  • 46,925
  • 11
  • 90
  • 115
  • How can you explain second approach? Where is division? – xiº Oct 07 '15 at 19:15
  • Is the default value for the keyword argument calculated every time the function is called? – Kyle Pittman Oct 07 '15 at 19:16
  • @Monkey No, default values are calculated once (on function definition) and stored on the created function object. – tzaman Oct 07 '15 at 19:16
  • @user3990145 The division happened during function definition and the resultant value was stored on the function object. When the function is called, all that's left to do is multiply that with `x`. – tzaman Oct 07 '15 at 19:25
  • 4
    Note that for Python 3, the peephole optimizer *does* precompute the `1/3` constant. (CPython specific, of course.) – Mark Dickinson Oct 07 '15 at 19:40
  • @MarkDickinson Yeah I was just trying that and noticed it myself, see edit. Enabling future-division in 2.7 does the trick too. Thanks for the comment! – tzaman Oct 07 '15 at 19:45