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

时间:2016-08-26 18:07:25

标签: python reflection metaprogramming indentation tokenize

从这样的事情:

print(get_indentation_level())

    print(get_indentation_level())

        print(get_indentation_level())

我想得到这样的东西:

1
2
3

代码能否以这种方式自行阅读?

我想要的只是嵌套更多嵌套部分的输出。与使代码更易于阅读的方式相同,它将使输出更易于阅读。

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

有没有更好的方法来避免手工格式化?

5 个答案:

答案 0 :(得分:113)

如果你想在嵌套级别而不是空格和制表符方面进行缩进,那么事情会变得棘手。例如,在以下代码中:

if True:
    print(
get_nesting_level())

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

print(1,
      2,
      get_nesting_level())

get_nesting_level的调用是嵌套的零级深度,尽管其行上存在前导空格。

在以下代码中:

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

if True:
    print(get_nesting_level())

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

在以下代码中:

if True: print(get_nesting_level())

是嵌套的零级别,还是一个?就形式语法中的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

答案 1 :(得分:21)

是的,这绝对有可能,这是一个有效的例子:

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()

答案 2 :(得分:9)

您可以使用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

如果你想根据带有:的prewouse行的缩进级别的数量,你可以稍微改变一下:

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]))

答案 3 :(得分:7)

要解决导致您的问题的“真实”问题,您可以实现一个上下文管理器,它跟踪缩进级别并使代码中的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

答案 4 :(得分:6)

>>> 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.