1

I have a functions for example like this


def first(x):
    return x * 2


def second(x):
    return x / 2

would be

a = first(2)
b = second(a)

more expensive because of that creating of variable a and checking type and calling it by python when using it, than doing something like this?


b = second(first(2))

Marcel Kopera
  • 179
  • 11
  • 1
    Why don't you time it? – NotAName Nov 18 '20 at 23:41
  • 2
    Maybe marginally, but in practice there is no difference. Even if you don't assign a name to it, Python should still store the result of the first call somewhere. Your specific example will also make use of the [small integer cache](https://stackoverflow.com/questions/306313/is-operator-behaves-unexpectedly-with-integers) so it's even less of an issue. – Selcuk Nov 18 '20 at 23:41
  • 2
    There's a difference of two instructions between the two ways according to `dis`, but I would expect these to perform identically. I would never worry about introducing a variable for performance reasons. Do what reads the nicest. – Carcigenicate Nov 18 '20 at 23:44
  • 1
    Yes. It will have an extra STORE_NAME op and an extra LOAD_NAME op. Cheap in a local scope (STORE_FAST / LOAD_FAST) and relatively expensive in a global scope. *That does not mean you should refactor your code to avoid the extra name lookup.* Almost surely a micro-optimization. – wim Nov 18 '20 at 23:44
  • thanks for the explanation. I'm writing a code where I call a function like this many times in a loop and using `np.array` inside of variable so I was wondering about that – Marcel Kopera Nov 18 '20 at 23:45
  • 1
    It depends on optimizations and stuff. Like if `a` was being stored in a register, then no. Otherwise, yes. However, the extra instruction cycle(s) is really not worth unreadable code – theEpsilon Nov 18 '20 at 23:45

1 Answers1

4

You can check the bytecode generated by using the dis module.

>>> dis.dis("b=second(first(2))")
  1           0 LOAD_NAME                0 (second)
              2 LOAD_NAME                1 (first)
              4 LOAD_CONST               0 (2)
              6 CALL_FUNCTION            1
              8 CALL_FUNCTION            1
             10 STORE_NAME               2 (b)
             12 LOAD_CONST               1 (None)
             14 RETURN_VALUE

>>> dis.dis("a=first(2); b=second(a)")
  1           0 LOAD_NAME                0 (first)
              2 LOAD_CONST               0 (2)
              4 CALL_FUNCTION            1
              6 STORE_NAME               1 (a)
              8 LOAD_NAME                2 (second)
             10 LOAD_NAME                1 (a)
             12 CALL_FUNCTION            1
             14 STORE_NAME               3 (b)
             16 LOAD_CONST               1 (None)
             18 RETURN_VALUE

The first case introduces 2 additional operations: a store and a load for a.

              6 STORE_NAME               1 (a)
             10 LOAD_NAME                1 (a)

However, there will be no noticeable difference in the runtime, and the first solution is always better for readability and debug.

abc
  • 11,579
  • 2
  • 26
  • 51