一行Python代码能知道它的缩进嵌套级别吗?



print(get_indentation_level())
    print(get_indentation_level())
        print(get_indentation_level())

我想要这样的东西:

1
2
3

代码能以这种方式读取自己吗?

我想要的只是代码中嵌套更多的部分的输出更嵌套。就像这会使代码更容易阅读一样,它也会使输出更容易阅读。

当然,我可以手动实现这一点,例如使用.format(),但我想到的是一个自定义打印函数,它将print(i*' ' + string),其中i是缩进级别。这将是在我的终端上生成可读输出的一种快速方法。

是否有更好的方法来避免辛苦的手动格式化?

如果您希望根据嵌套级别而不是空格和制表符缩进,事情就变得棘手了。例如,在以下代码中:

if True:
    print(
get_nesting_level())

get_nesting_level的调用实际上嵌套了一层深,尽管在get_nesting_level调用的行上没有前导空格。同时,在以下代码中:

print(1,
      2,
      get_nesting_level())

调用get_nesting_level的嵌套深度为0级,尽管该行存在前导空格。

在以下代码中:

if True:
  if True:
    print(get_nesting_level())
if True:
    print(get_nesting_level())

get_nesting_level的两个调用位于不同的嵌套级别,尽管前导空格是相同的。

在以下代码中:

if True: print(get_nesting_level())

是嵌套的0级还是1级?就形式语法中的INDENTDEDENT标记而言,它是零级深度,但您可能会有不同的感觉。


如果您想这样做,您将不得不对整个文件进行标记,直到调用点,并计数INDENTDEDENT标记。tokenize模块对于这样的函数非常有用:

import inspect
import tokenize
def get_nesting_level():
    caller_frame = inspect.currentframe().f_back
    filename, caller_lineno, _, _, _ = inspect.getframeinfo(caller_frame)
    with open(filename) as f:
        indentation_level = 0
        for token_record in tokenize.generate_tokens(f.readline):
            token_type, _, (token_lineno, _), _, _ = token_record
            if token_lineno > caller_lineno:
                break
            elif token_type == tokenize.INDENT:
                indentation_level += 1
            elif token_type == tokenize.DEDENT:
                indentation_level -= 1
        return indentation_level

是的,这绝对是可能的,这里有一个工作示例:

import inspect
def get_indentation_level():
    callerframerecord = inspect.stack()[1]
    frame = callerframerecord[0]
    info = inspect.getframeinfo(frame)
    cc = info.code_context[0]
    return len(cc) - len(cc.lstrip())
if 1:
    print get_indentation_level()
    if 1:
        print get_indentation_level()
        if 1:
            print get_indentation_level()

您可以使用sys.current_frame.f_lineno来获取行号。然后,为了找到缩进的数量你需要找到前一行零缩进的数量然后用该行号减去当前行号你会得到缩进的数量:

import sys
current_frame = sys._getframe(0)
def get_ind_num():
    with open(__file__) as f:
        lines = f.readlines()
    current_line_no = current_frame.f_lineno
    to_current = lines[:current_line_no]
    previous_zoro_ind = len(to_current) - next(i for i, line in enumerate(to_current[::-1]) if not line[0].isspace())
    return current_line_no - previous_zoro_ind
演示:

if True:
    print get_ind_num()
    if True:
        print(get_ind_num())
        if True:
            print(get_ind_num())
            if True: print(get_ind_num())
# Output
1
3
5
6

如果你想用:在前面的行基础上缩进的数量,你可以做一点改变:

def get_ind_num():
    with open(__file__) as f:
        lines = f.readlines()
    current_line_no = current_frame.f_lineno
    to_current = lines[:current_line_no]
    previous_zoro_ind = len(to_current) - next(i for i, line in enumerate(to_current[::-1]) if not line[0].isspace())
    return sum(1 for line in lines[previous_zoro_ind-1:current_line_no] if line.strip().endswith(':'))
演示:

if True:
    print get_ind_num()
    if True:
        print(get_ind_num())
        if True:
            print(get_ind_num())
            if True: print(get_ind_num())
# Output
1
2
3
3

作为另一种答案,这里有一个函数用于获取缩进(空格)的数量:

import sys
from itertools import takewhile
current_frame = sys._getframe(0)
def get_ind_num():
    with open(__file__) as f:
        lines = f.readlines()
    return sum(1 for _ in takewhile(str.isspace, lines[current_frame.f_lineno - 1]))

要解决导致你的问题的"真正"问题,你可以实现一个上下文管理器,它跟踪缩进级别,并使代码中的with块结构对应于输出的缩进级别。这样,代码缩进仍然反映输出缩进,而不会过多地耦合两者。仍然可以将代码重构为不同的函数,并根据代码结构进行其他缩进,而不会干扰输出缩进。

#!/usr/bin/env python
# coding: utf8
from __future__ import absolute_import, division, print_function

class IndentedPrinter(object):
    def __init__(self, level=0, indent_with='  '):
        self.level = level
        self.indent_with = indent_with
    def __enter__(self):
        self.level += 1
        return self
    def __exit__(self, *_args):
        self.level -= 1
    def print(self, arg='', *args, **kwargs):
        print(self.indent_with * self.level + str(arg), *args, **kwargs)

def main():
    indented = IndentedPrinter()
    indented.print(indented.level)
    with indented:
        indented.print(indented.level)
        with indented:
            indented.print('Hallo', indented.level)
            with indented:
                indented.print(indented.level)
            indented.print('and back one level', indented.level)

if __name__ == '__main__':
    main()
输出:

0
  1
    Hallo 2
      3
    and back one level 2
>>> import inspect
>>> help(inspect.indentsize)
Help on function indentsize in module inspect:
indentsize(line)
    Return the indent size, in spaces, at the start of a line of text.

最新更新