Reputation: 8778
I do this:
>>> dis.dis(lambda: 1 + 1)
0 LOAD_CONST 2 (2)
3 RETURN_VALUE
I was expecting a BINARY_ADD opcode to perform the addition. How was the sum computed?
Upvotes: 6
Views: 205
Reputation: 239473
This is the work of Python's peephole optimizer. It evaluates simple operations with only constants during the compile time itself and stores the result as a constant in the generated bytecode.
Quoting from the Python 2.7.9 Source code,
/* Fold binary ops on constants.
LOAD_CONST c1 LOAD_CONST c2 BINOP --> LOAD_CONST binop(c1,c2) */
case BINARY_POWER:
case BINARY_MULTIPLY:
case BINARY_TRUE_DIVIDE:
case BINARY_FLOOR_DIVIDE:
case BINARY_MODULO:
case BINARY_ADD:
case BINARY_SUBTRACT:
case BINARY_SUBSCR:
case BINARY_LSHIFT:
case BINARY_RSHIFT:
case BINARY_AND:
case BINARY_XOR:
case BINARY_OR:
if (lastlc >= 2 &&
ISBASICBLOCK(blocks, i-6, 7) &&
fold_binops_on_constants(&codestr[i-6], consts)) {
i -= 2;
assert(codestr[i] == LOAD_CONST);
cumlc = 1;
}
break;
Basically, it looks for instructions like this
LOAD_CONST c1
LOAD_CONST c2
BINARY_OPERATION
and evaluates that and replaces those instructions with the result and a LOAD_CONST
instruction. Quoting the comment in the fold_binops_on_constants
function,
/* Replace LOAD_CONST c1. LOAD_CONST c2 BINOP
with LOAD_CONST binop(c1,c2)
The consts table must still be in list form so that the
new constant can be appended.
Called with codestr pointing to the first LOAD_CONST.
Abandons the transformation if the folding fails (i.e. 1+'a').
If the new constant is a sequence, only folds when the size
is below a threshold value. That keeps pyc files from
becoming large in the presence of code like: (None,)*1000.
*/
The actual evaluation of this particular code happens in this block,
case BINARY_ADD:
newconst = PyNumber_Add(v, w);
break;
Upvotes: 10
Reputation: 10914
The Python interpreter interprets from the inside out, that is, it reads the 1 + 1
evaluates it to 2
, then creates a function object that returns the constant 2
(notice the order here!). Finally, the dis
function evaluates. the newly created lambda function object, which simply returns a 2
.
Thus, the 1+1
has already been computed when the lambda function object is created, and the dis.dis()
function knows nothing about the addition that took place when the interpreter read 1+1
and evaluated it to 2
.
If you do something like:
>>> dis.dis(lambda: x + 1)
1 0 LOAD_GLOBAL 0 (x)
3 LOAD_CONST 1 (1)
6 BINARY_ADD
7 RETURN_VALUE
You'll notice that a BINARY_ADD instruction is used, since x + 1
can't be further simplified by itself.
Upvotes: 1