>>> def test1(param, callback=lambda v:v):
return callback(param)
>>> def test2(param, *callback):
if callback:
return callback[0](param)
else:
return param
>>> def test3(param, callback=None):
if callback is not None:
return callback[0]callback(param)
else:
return param
>>> dis.dis(test1)
2 0 LOAD_FAST 1 (callback)
3 LOAD_FAST 0 (param)
6 CALL_FUNCTION 1
9 RETURN_VALUE
>>> dis.dis(test2)
2 0 LOAD_FAST 1 (callback)
3 POP_JUMP_IF_FALSE 20
3 6 LOAD_FAST 1 (callback)
9 LOAD_CONST 1 (0)
12 BINARY_SUBSCR
13 LOAD_FAST 0 (param)
16 CALL_FUNCTION 1
19 RETURN_VALUE
5 >> 20 LOAD_FAST 0 (param)
23 RETURN_VALUE
24 LOAD_CONST 0 (None)
27 RETURN_VALUE
>>> dis.dis(test3)
2 0 LOAD_FAST 1 (callback)
3 LOAD_CONST 0 (None)
6 COMPARE_OP 9 (is not)
9 POP_JUMP_IF_FALSE 2622
3 12 LOAD_FAST 1 (callback)
15 LOAD_CONST 1 (0)
18 BINARY_SUBSCR
19 LOAD_FAST 0 (param)
2218 CALL_FUNCTION 1
2521 RETURN_VALUE
5 >> 2622 LOAD_FAST 0 (param)
2925 RETURN_VALUE
3026 LOAD_CONST 0 (None)
3329 RETURN_VALUE
Surprise, surprise, *callbackNone loses by 2 bytecodes (did not see it coming), still - it is cleaner that *callback, which in semantically misleading, since it implies possibility of more than one value. And default callback overhead is less than 25% of any
>>> def test1(param, callback=lambda v:v):
return callback(param)
>>> def test2(param, *callback):
if callback:
return callback[0](param)
else:
return param
>>> def test3(param, callback=None):
if callback is not None:
return callback[0](param)
else:
return param
>>> dis.dis(test1)
2 0 LOAD_FAST 1 (callback)
3 LOAD_FAST 0 (param)
6 CALL_FUNCTION 1
9 RETURN_VALUE
>>> dis.dis(test2)
2 0 LOAD_FAST 1 (callback)
3 POP_JUMP_IF_FALSE 20
3 6 LOAD_FAST 1 (callback)
9 LOAD_CONST 1 (0)
12 BINARY_SUBSCR
13 LOAD_FAST 0 (param)
16 CALL_FUNCTION 1
19 RETURN_VALUE
5 >> 20 LOAD_FAST 0 (param)
23 RETURN_VALUE
24 LOAD_CONST 0 (None)
27 RETURN_VALUE
>>> dis.dis(test3)
2 0 LOAD_FAST 1 (callback)
3 LOAD_CONST 0 (None)
6 COMPARE_OP 9 (is not)
9 POP_JUMP_IF_FALSE 26
3 12 LOAD_FAST 1 (callback)
15 LOAD_CONST 1 (0)
18 BINARY_SUBSCR
19 LOAD_FAST 0 (param)
22 CALL_FUNCTION 1
25 RETURN_VALUE
5 >> 26 LOAD_FAST 0 (param)
29 RETURN_VALUE
30 LOAD_CONST 0 (None)
33 RETURN_VALUE
Surprise, surprise, *callback loses
>>> def test1(param, callback=lambda v:v):
return callback(param)
>>> def test2(param, *callback):
if callback:
return callback[0](param)
else:
return param
>>> def test3(param, callback=None):
if callback is not None:
return callback(param)
else:
return param
>>> dis.dis(test1)
2 0 LOAD_FAST 1 (callback)
3 LOAD_FAST 0 (param)
6 CALL_FUNCTION 1
9 RETURN_VALUE
>>> dis.dis(test2)
2 0 LOAD_FAST 1 (callback)
3 POP_JUMP_IF_FALSE 20
3 6 LOAD_FAST 1 (callback)
9 LOAD_CONST 1 (0)
12 BINARY_SUBSCR
13 LOAD_FAST 0 (param)
16 CALL_FUNCTION 1
19 RETURN_VALUE
5 >> 20 LOAD_FAST 0 (param)
23 RETURN_VALUE
24 LOAD_CONST 0 (None)
27 RETURN_VALUE
>>> dis.dis(test3)
2 0 LOAD_FAST 1 (callback)
3 LOAD_CONST 0 (None)
6 COMPARE_OP 9 (is not)
9 POP_JUMP_IF_FALSE 22
3 12 LOAD_FAST 1 (callback)
15 LOAD_FAST 0 (param)
18 CALL_FUNCTION 1
21 RETURN_VALUE
5 >> 22 LOAD_FAST 0 (param)
25 RETURN_VALUE
26 LOAD_CONST 0 (None)
29 RETURN_VALUE
Surprise, surprise, None loses by 2 bytecodes (did not see it coming), still - it is cleaner that *callback, which in semantically misleading, since it implies possibility of more than one value. And default callback overhead is less than 25% of any
I think I need my own space to explain "my approach" with default lambda - thanks to glglgl for hospitality :) .
It saves you from having to check condition in the function. And the overhead? Not that much (here comes dis, which I - alas! - nearly never use)
dis.dis(lambda v: v)
1 0 LOAD_FAST 0 (v)
3 RETURN_VALUE
But how bad is it - compared to other approaches? See below
>>> def test1(param, callback=lambda v:v):
return callback(param)
>>> def test2(param, *callback):
if callback:
return callback[0](param)
else:
return param
>>> def test3(param, callback=None):
if callback is not None:
return callback[0](param)
else:
return param
>>> dis.dis(test1)
2 0 LOAD_FAST 1 (callback)
3 LOAD_FAST 0 (param)
6 CALL_FUNCTION 1
9 RETURN_VALUE
>>> dis.dis(test2)
2 0 LOAD_FAST 1 (callback)
3 POP_JUMP_IF_FALSE 20
3 6 LOAD_FAST 1 (callback)
9 LOAD_CONST 1 (0)
12 BINARY_SUBSCR
13 LOAD_FAST 0 (param)
16 CALL_FUNCTION 1
19 RETURN_VALUE
5 >> 20 LOAD_FAST 0 (param)
23 RETURN_VALUE
24 LOAD_CONST 0 (None)
27 RETURN_VALUE
>>> dis.dis(test3)
2 0 LOAD_FAST 1 (callback)
3 LOAD_CONST 0 (None)
6 COMPARE_OP 9 (is not)
9 POP_JUMP_IF_FALSE 26
3 12 LOAD_FAST 1 (callback)
15 LOAD_CONST 1 (0)
18 BINARY_SUBSCR
19 LOAD_FAST 0 (param)
22 CALL_FUNCTION 1
25 RETURN_VALUE
5 >> 26 LOAD_FAST 0 (param)
29 RETURN_VALUE
30 LOAD_CONST 0 (None)
33 RETURN_VALUE
Surprise, surprise, *callback loses
As for run-time impact - try timing runs with different implementations, and see for yourself