pythonpython-3.6decoratorpython-decoratorsinspect

Why does inspect return different line for class inheriting from superclass?


While trying to figure out if a function is called with the @decorator syntax, we realized that inspect has a different behaviour when looking at a decorated class that inherits from a superclass.

The following behaviour was found with CPython 3.6.2 under Windows.

It was also reproduced in CPython 3.7.0 under Linux.

import inspect

def decorate(f):
    lines = inspect.stack()[1].code_context
    print(f.__name__, lines)

    return f

@decorate
class Foo:
    pass

@decorate
class Bar(dict):
    pass

Output

Foo ['@decorate\n']
Bar ['class Bar(dict):\n']

Why does inheritance change the behaviour of inspect?


Solution

  • Further experiment shows that this is a quirk of Python's line number assignment. Particularly, if we use dis to see the disassembly of code with and without a base class:

    import dis
    import sys
    
    dis.dis(sys._getframe().f_code)
    
    def dec(): pass
    
    @dec
    class Foo: pass
    
    @dec
    class Bar(Foo): pass
    

    We see that for Foo, the instructions involved have line number 8 (corresponding to the @dec line):

      8          58 LOAD_NAME                4 (dec)
                 61 LOAD_BUILD_CLASS
                 62 LOAD_CONST               4 (<code object Foo at 0x2b2a65422810, file "./prog.py", line 8>)
                 65 LOAD_CONST               5 ('Foo')
                 68 MAKE_FUNCTION            0
                 71 LOAD_CONST               5 ('Foo')
                 74 CALL_FUNCTION            2 (2 positional, 0 keyword pair)
                 77 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
                 80 STORE_NAME               5 (Foo)
    

    But for Bar, the line number advances from 11 to 12 for the LOAD_NAME that loads the base class:

     11          83 LOAD_NAME                4 (dec)
                 86 LOAD_BUILD_CLASS
                 87 LOAD_CONST               6 (<code object Bar at 0x2b2a654a0f60, file "./prog.py", line 11>)
                 90 LOAD_CONST               7 ('Bar')
                 93 MAKE_FUNCTION            0
                 96 LOAD_CONST               7 ('Bar')
    
     12          99 LOAD_NAME                5 (Foo)
                102 CALL_FUNCTION            3 (3 positional, 0 keyword pair)
                105 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
                108 STORE_NAME               6 (Bar)
    

    With no base class, the parent frame's f_lineno is on the @ line when the decorator runs. With a base class, the parent frame is on the load-the-base-class line.