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

从这样的东西:

print(get_indentation_level())


print(get_indentation_level())


print(get_indentation_level())

我想要这样的东西:

1
2
3

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

我所需要的只是代码中嵌套程度更高的部分的输出。同样地,这使得代码更容易阅读,它也使得输出更容易阅读。

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

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

9387 次浏览
>>> 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.

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

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

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

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

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

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