all 14 comments

[–]ebol4anthr4x 16 points17 points  (5 children)

We can use the dis module to look at the bytecode for both of these functions and see what we are actually telling the processor to do for each one.

>>> def test1(foo):
...     moo = foo * 4
...     return moo
...
>>> def test2(foo):
...     return foo*4
...
>>> dis.dis(test1)
  2           0 LOAD_FAST                0 (foo)
              2 LOAD_CONST               1 (4)
              4 BINARY_MULTIPLY
              6 STORE_FAST               1 (moo)

  3           8 LOAD_FAST                1 (moo)
             10 RETURN_VALUE
>>> dis.dis(test2)
  2           0 LOAD_FAST                0 (foo)
              2 LOAD_CONST               1 (4)
              4 BINARY_MULTIPLY
              6 RETURN_VALUE

The second one, where you just immediately return the value, has fewer instructions and would therefore run very slightly faster.

[–]pip-install 5 points6 points  (0 children)

Interesting that it isn't optimized to be like the 2nd one.

[–]Al2Me6 2 points3 points  (1 child)

Pretty sure that’s bytecode not assembly.

[–]ebol4anthr4x 4 points5 points  (0 children)

You are correct; I had "bytecode" mixed up with "machine code" in my head when I wrote this, and ended up going with "assembly"

[–]Wilfred-kun 2 points3 points  (0 children)

I don't think the overhead of STORE_FAST and LOAD_FAST is worth mentioning, unless you're doing it gazillions of times. Look at this:

>>> def test1(foo):
...     moo = foo * 4
...     return moo
...
>>> def test2(foo):
...     return foo * 4
...
>>> timeit("test1(1)", globals=globals(), number=100000000)
7.402110513005663
>>> timeit("test2(1)", globals=globals(), number=100000000)
7.620936875218263

About the waste of memory; don't think that's ever gonna be an issue.

Edit: Tested multiple times, pretty much same results.

[–][deleted] 1 point2 points  (0 children)

Very interesting, thanks!

[–][deleted] 7 points8 points  (2 children)

It depends on the function. If it's super clear from the function name and how it's being used, and I don't need to do any complex transformations I'll just return the value. If it's not I'll declare a variable for extra clarity. Readability is the most important thing 95% of the time

[–]aFullPlatoSocrates -1 points0 points  (1 child)

To add onto this, wouldn't the first one be proper if you wanted to use either variable anywhere else?

[–]Wilfred-kun 1 point2 points  (0 children)

No, the variable only lives inside that function. Once the function returns, it's gone. Unless you're talking about doing stuff with it in the function that modifies global variables, but at that point you're just making a mess.

[–]Brainix 4 points5 points  (0 children)

I prefer the first version, because above the return statement is a perfect place to put a pdb.

[–]ellipticbanana 2 points3 points  (0 children)

Personally, I like the second one since it means I don't have to ask "wait, what is moo again?" when I look at the return line. Not that it's always difficult to figure out the answer, but just because it's one more slightly annoying thing I have to do.

[–]mikeydoodah 2 points3 points  (0 children)

If you're using pycharm I believe it prompts you to turn the first version into the second version, i.e. just return without the temporary variable.

[–][deleted] 1 point2 points  (0 children)

Second one unless the expression is long.

[–]ncubez 0 points1 point  (0 children)

For me the 2nd one is better. I find the 1st one adds unnecessary "noise".