一行Python的代码能知道它的缩进嵌套层级吗?
Can a line of Python code know its indentation nesting level?
来自这样的东西:
print(get_indentation_level())
print(get_indentation_level())
print(get_indentation_level())
我想得到这样的东西:
1
2
3
代码可以这样读取自己吗?
我想要的只是代码嵌套更多部分的输出嵌套更多。就像这使代码更易于阅读一样,它也会使输出更易于阅读。
当然我可以手动实现这个,例如使用.format()
,但我想到的是一个自定义打印函数,它将 print(i*' ' + string)
其中 i
是缩进级别。这将是在我的终端上生成可读输出的快速方法。
是否有更好的方法来避免费力的手动格式化?
>>> 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())
嵌套的是零级还是一级?就形式语法中的 INDENT
和 DEDENT
标记而言,它是零级深度,但您可能不会有同样的感觉。
如果你想这样做,你将不得不标记整个文件直到调用点并计算 INDENT
和 DEDENT
个标记。 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
来自这样的东西:
print(get_indentation_level())
print(get_indentation_level())
print(get_indentation_level())
我想得到这样的东西:
1
2
3
代码可以这样读取自己吗?
我想要的只是代码嵌套更多部分的输出嵌套更多。就像这使代码更易于阅读一样,它也会使输出更易于阅读。
当然我可以手动实现这个,例如使用.format()
,但我想到的是一个自定义打印函数,它将 print(i*' ' + string)
其中 i
是缩进级别。这将是在我的终端上生成可读输出的快速方法。
是否有更好的方法来避免费力的手动格式化?
>>> 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())
嵌套的是零级还是一级?就形式语法中的 INDENT
和 DEDENT
标记而言,它是零级深度,但您可能不会有同样的感觉。
如果你想这样做,你将不得不标记整个文件直到调用点并计算 INDENT
和 DEDENT
个标记。 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