Python: infos about the implementation of a Python function
Question: I'm discovering the CPython implementation, the structure of Python objects
and the Python bytecodes.

Playing with functions, I've found out that empty functions have a stack size
of 1.  
Why? **What var is declared to occupy the stack space?**

  
**Empty function:**

    
    
    def empty():
        pass
    

**Function infos:**

    
    
    >>> dis.show_code(empty)
    
    Name:                empty
    Filename:            <pyshell#27>
    Argument count:      0
    Kw-only arguments:   0
    Stack size:          1
    Number of locals:    0
    Variable names:
    Constants:
        0: None
    
    Names:
    Flags:               OPTIMIZED, NEWLOCALS, NOFREE
    First line number:   1
    Free variables:
    Cell variables:
    

  
  
**Function with locals:**

    
    
    def withlocals():
        first = 0
        second = [1, 2, 3]
    

**Function infos:**

    
    
    >>> dis.show_code(withlocals)
    
    Name:                withlocals
    Filename:            <pyshell#27>
    Argument count:      0
    Kw-only arguments:   0
    Stack size:          3
    Number of locals:    2
    Variable names:
        0: first
        1: second
    
    Constants:
        0: None
        1: 0
        2: 1
        3: 2
        4: 3
    
    Names:
    Flags:               OPTIMIZED, NEWLOCALS, NOFREE
    First line number:   1
    Free variables:
    Cell variables:
    


Answer: The `stack_size` is the _upper bound_ of the stack usage by the interpreter
opcodes. However, the analysis has some
[bugs](https://bugs.python.org/issue26204) and another, larger one at the end
of this post, so the bound is not tight.

    
    
    >>> def empty():
    ...     pass
    ... 
    >>> import dis
    >>> dis.dis(empty)
      2           0 LOAD_CONST               0 (None)
                  3 RETURN_VALUE        
    

An empty function returns `None`. It requires 1 item of stack to load the
reference to `None` on top of stack; `RETURN_VALUE` returns the value that is
stored on top of stack.

The local variables themselves are not included in this count, which is very
evident from

    
    
    >>> def many_vars():
    ...     a = 1
    ...     b = 2
    ...     c = 3
    ...     d = 4
    ...     e = 5
    ...     f = 6
    ...     g = 7
    ... 
    >>> many_vars.__code__.co_stacksize
    1
    

* * *

In the case of

    
    
    def withlocals():
        first = 0
        second = [1, 2, 3]
    

the stack must be large enough to build the list of 3. If you add elements to
the list, the stack grows by that amount. I've added the size of the stack at
each point to the dump:

    
    
    >>> dis.dis(withlocals)
      2           0 LOAD_CONST               1 (0)          1
                  3 STORE_FAST               0 (first)      0
    
      3           6 LOAD_CONST               2 (1)          1
                  9 LOAD_CONST               3 (2)          2
                 12 LOAD_CONST               4 (3)          3
                 15 BUILD_LIST               3              1
                 18 STORE_FAST               1 (second)     0
                 21 LOAD_CONST               0 (None)       1
                 24 RETURN_VALUE                            0
    

However the analysis seems to have bugs when it comes to tuple constants:

    
    
    >>> def a_long_tuple():
    ...     first = (0, 0, 0, 0, 0, 0, 0)
    ... 
    ...
    >>> dis.dis(a_long_tuple)
      2           0 LOAD_CONST               2 ((0, 0, 0, 0, 0, 0, 0))
                  3 STORE_FAST               0 (first)
                  6 LOAD_CONST               0 (None)
                  9 RETURN_VALUE        
    >>> dis.show_code(a_long_tuple)
    Name:              withlocals
    Filename:          <stdin>
    Argument count:    0
    Kw-only arguments: 0
    Number of locals:  1
    Stack size:        7
    Flags:             OPTIMIZED, NEWLOCALS, NOFREE
    Constants:
       0: None
       1: 0
       2: (0, 0, 0, 0, 0, 0, 0)
    Variable names:
       0: first
    

The code only has one tuple, that is a constant, yet the analysis claims it
requires stack space of 7, in both Python 2 and 3!

The reason for that is that the assembled code for building a constant tuple
is initially identical to building a list, except with `BUILD_TUPLE` opcode at
the end; but the [peephole optimizer
optimizes](https://github.com/python/cpython/blob/c448fb502e87bfa73c1bf0c2bd78f5176feaa52d/Python/compile.c#L4732)
that into `LOAD_CONST` from partial assembler output. However the
[`co_stacksize`](https://github.com/python/cpython/blob/c448fb502e87bfa73c1bf0c2bd78f5176feaa52d/Python/compile.c#L4745)
is calculated based on the original assembled code!

