This is really unfair to the max function because of the generator expression you feed it.
For each call to f2 you need to create a new closure for n , you need to execute a new function (that both expression expressions and expressions in lists with Python 3 are implemented, see 'Details' of PEP 289 ), which completes the code object representing gen-exp. Then this function, iteratively calling other functions, is called again.
A small piece of byte code:
14 LOAD_CLOSURE 0 (n) 16 BUILD_TUPLE 1 18 LOAD_CONST 2 (<code object <genexpr> at 0x7f1b667e1f60, file "", line 16>) 20 LOAD_CONST 3 ('f2.<locals>.<genexpr>') 22 MAKE_FUNCTION 8 24 LOAD_CONST 5 ((1, 2)) 26 GET_ITER 28 CALL_FUNCTION 1
Of course, you do not see any instructions like these in the case of f1 , since it just makes calls.
When you then call your function max , f2 , a significant number of times, as you do when recursively calculating factorial 30 , the overhead just accumulates.
The version with the concept of a function function list suffers to a large extent from the same problem. This is a little faster because comprehension of lists is faster than generator expressions.
If I use max(a, b) instead of max(iterable) , the performance mismatch disappears.
Precisely, no functions are created for each call in this case, so you do not see that overhead is accumulating. You just provide the arguments here.
Jim fasarakis hilliard
source share