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 10.
It was also reproduced in CPython 3.7.0 under Linux 64 bits.
import inspectdef decorate(f):lines = inspect.stack()[1].code_contextprint(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
?
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 sysdis.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_CLASS62 LOAD_CONST 4 (<code object Foo at 0x2b2a65422810, file "./prog.py", line 8>)65 LOAD_CONST 5 ('Foo')68 MAKE_FUNCTION 071 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_CLASS87 LOAD_CONST 6 (<code object Bar at 0x2b2a654a0f60, file "./prog.py", line 11>)90 LOAD_CONST 7 ('Bar')93 MAKE_FUNCTION 096 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.