Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p>The bytecode is not actually interpreted to machine code, unless you are using some exotic implementation such as pypy.</p> <p>Other than that, you have the description correct. The bytecode is loaded into the Python runtime and interpreted by a virtual machine, which is a piece of code that reads each instruction in the bytecode and executes whatever operation is indicated. You can see this bytecode with the <code>dis</code> module, as follows:</p> <pre><code>&gt;&gt;&gt; def fib(n): return n if n &lt; 2 else fib(n - 2) + fib(n - 1) ... &gt;&gt;&gt; fib(10) 55 &gt;&gt;&gt; import dis &gt;&gt;&gt; dis.dis(fib) 1 0 LOAD_FAST 0 (n) 3 LOAD_CONST 1 (2) 6 COMPARE_OP 0 (&lt;) 9 JUMP_IF_FALSE 5 (to 17) 12 POP_TOP 13 LOAD_FAST 0 (n) 16 RETURN_VALUE &gt;&gt; 17 POP_TOP 18 LOAD_GLOBAL 0 (fib) 21 LOAD_FAST 0 (n) 24 LOAD_CONST 1 (2) 27 BINARY_SUBTRACT 28 CALL_FUNCTION 1 31 LOAD_GLOBAL 0 (fib) 34 LOAD_FAST 0 (n) 37 LOAD_CONST 2 (1) 40 BINARY_SUBTRACT 41 CALL_FUNCTION 1 44 BINARY_ADD 45 RETURN_VALUE &gt;&gt;&gt; </code></pre> <h2>Detailed explanation</h2> <p>It is quite important to understand that the above code is never executed by your CPU; nor is it ever converted into something that is (at least, not on the official C implementation of Python). The CPU executes the virtual machine code, which performs the work indicated by the bytecode instructions. When the interpreter wants to execute the <code>fib</code> function, it reads the instructions one at a time, and does what they tell it to do. It looks at the first instruction, <code>LOAD_FAST 0</code>, and thus grabs parameter 0 (the <code>n</code> passed to <code>fib</code>) from wherever parameters are held and pushes it onto the interpreter's stack (Python's interpreter is a stack machine). On reading the next instruction, <code>LOAD_CONST 1</code>, it grabs constant number 1 from a collection of constants owned by the function, which happens to be the number 2 in this case, and pushes that onto the stack. You can actually see these constants:</p> <pre><code>&gt;&gt;&gt; fib.func_code.co_consts (None, 2, 1) </code></pre> <p>The next instruction, <code>COMPARE_OP 0</code>, tells the interpreter to pop the two topmost stack elements and perform an inequality comparison between them, pushing the Boolean result back onto the stack. The fourth instruction determines, based on the Boolean value, whether to jump forward five instructions or continue on with the next instruction. All that verbiage explains the <code>if n &lt; 2</code> part of the conditional expression in <code>fib</code>. It will be a highly instructive exercise for you to tease out the meaning and behaviour of the rest of the <code>fib</code> bytecode. The only one, I'm not sure about is <code>POP_TOP</code>; I'm guessing <code>JUMP_IF_FALSE</code> is defined to leave its Boolean argument on the stack rather than popping it, so it has to be popped explicitly.</p> <p>Even more instructive is to inspect the raw bytecode for <code>fib</code> thus:</p> <pre><code>&gt;&gt;&gt; code = fib.func_code.co_code &gt;&gt;&gt; code '|\x00\x00d\x01\x00j\x00\x00o\x05\x00\x01|\x00\x00S\x01t\x00\x00|\x00\x00d\x01\x00\x18\x83\x01\x00t\x00\x00|\x00\x00d\x02\x00\x18\x83\x01\x00\x17S' &gt;&gt;&gt; import opcode &gt;&gt;&gt; op = code[0] &gt;&gt;&gt; op '|' &gt;&gt;&gt; op = ord(op) &gt;&gt;&gt; op 124 &gt;&gt;&gt; opcode.opname[op] 'LOAD_FAST' &gt;&gt;&gt; </code></pre> <p>Thus you can see that the first byte of the bytecode is the <code>LOAD_FAST</code> instruction. The next pair of bytes, <code>'\x00\x00'</code> (the number 0 in 16 bits) is the argument to <code>LOAD_FAST</code>, and tells the bytecode interpreter to load parameter 0 onto the stack.</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload