vgbcell
vgbcell

Reputation: 199

Python inspect.stack()'s code_context only returns one line of context

import inspect

def a(x, y, z):
  frame = inspect.stack()[1]
  print(frame.code_context)

def b():
  a(
    1,
    2,
    3,
  )

b()

>> ['    3,\n']

Why is code_context only returning the last line?? The documentation says code_context is "a list of lines of context from the source code"

I would expect the code_context to include something along the lines of:

[
  '    1,\n',
  '    2,\n',
  '    3,\n',
]

I would like to obtain all lines of context.

Upvotes: 0

Views: 2116

Answers (2)

blhsing
blhsing

Reputation: 106543

For a robust solution that preserves white spaces and comments, you can use the lib2to3 module to parse and scrape all the simple statement nodes (denoted by simple_stmt in the Python grammar from Grammar.txt) from the file the parent frame belongs to, and return the node whose starting line number and the number of lines (calculated by counting the number of newline characters) cover the line number of the frame. Note that the statement node does not contain the preceding indentation if it's at the beginning of a code block, however, so you would have to obtain the indentation from its previous sibling node if it is a node of white spaces:

from lib2to3 import fixer_base, refactor

class StatementScraper(fixer_base.BaseFix):
    PATTERN = 'simple_stmt'

    def __init__(self, lineno):
        super().__init__(None, None)
        self.lineno = lineno
        self.statement = ''

    def transform(self, node, result):
        if not self.statement and self.lineno - node.get_lineno() < str(node).count('\n'):
            prev_sibling = str(node.prev_sibling)
            if prev_sibling.isspace():
                self.statement += prev_sibling.lstrip('\n')
            self.statement += str(node)
        return node

class get_statement(refactor.RefactoringTool):
    def __init__(self, source, lineno):
        self.source = source
        self.scraper = StatementScraper(lineno)
        super().__init__(None)

    def get_fixers(self):
        return [self.scraper], []

    def __str__(self):
        self.refactor_string(self.source, '')
        return self.scraper.statement

so that:

import inspect

def a(x, y, z):
    frame = inspect.stack()[1].frame
    print(get_statement(open(frame.f_code.co_filename).read(), frame.f_lineno))

def b():
    a(
        1,
        2,
        3,
    )

b()

outputs:

    a(
        1,
        2,
        3,
    )

Demo: https://repl.it/@blhsing/HotpinkOpenCgibin

Upvotes: 3

blhsing
blhsing

Reputation: 106543

The Python interpreter retains only a single line number for each frame unfortunately. One relatively easy and robust way to seeing the entire code context is by decompiling the code object using the excellent uncompyle6 module. Since the code object does not retain white spaces and comments, you'll see the more compact version of your original code as the decompiled output:

import inspect
from io import StringIO
from uncompyle6 import deparse_code2str

def a(x, y, z):
    frame = inspect.stack()[1]
    code = StringIO()
    deparse_code2str(frame.frame.f_code, out=code)
    print(code.getvalue())

def b():
    a(
        1,
        2,
        3,
    )

b()

This outputs:

a(1, 2, 3)

Demo: https://repl.it/@blhsing/PastelPunyProcess

Upvotes: 2

Related Questions