4

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:
4
  • The function itself, presumably - it's just another object in Python. Commented Mar 12, 2016 at 16:59
  • Yeah, and what does the base stacksize mean? Commented Mar 12, 2016 at 17:09
  • A guess: if functions could have stack size zero when they have no local variables, then a function that calls another function could result in both having the same address. Commented Mar 12, 2016 at 17:39
  • @NirFriedman I don't think I got it... A Python function is a C struct with a determinate number of elements and there's no chance to the size of an instance of that struct to be 0... I understood from Antti Haapala that this size refers to the stack used to create the real vars. Commented Mar 12, 2016 at 19:42

1 Answer 1

5

The stack_size is the upper bound of the stack usage by the interpreter opcodes. However, the analysis has some bugs 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 that into LOAD_CONST from partial assembler output. However the co_stacksize is calculated based on the original assembled code!

Sign up to request clarification or add additional context in comments.

2 Comments

Ok, so this stack is used by the Py compiler and the size is referred to the necessary space to generate the PyObjects vars (int, list, tuple...), not to the frame stack of the running code, isn't it?
Python bytecode uses a stack-based virtual machine. It means that all opcodes access stack, there are no registers. The stacksize is the conservative upper limit estimate that the byte code would need stack space.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.