Может ли строка кода Python знать уровень вложенности вложения?
От чего-то вроде этого:
print(get_indentation_level())
print(get_indentation_level())
print(get_indentation_level())
Я хотел бы получить что-то вроде этого:
1
2
3
Может ли код считывать себя таким образом?
Все, что я хочу, это вывод из более вложенных частей кода, чтобы он был более вложенным. Точно так же, как это упрощает чтение кода, это облегчит чтение.
Конечно, я мог бы реализовать это вручную, используя, например, .format()
, но я имел в виду специальную функцию печати, которая print(i*' ' + string)
, где i
является уровнем отступа. Это будет быстрый способ сделать читаемый вывод на моем терминале.
Есть ли лучший способ сделать это, чтобы избежать кропотливого ручного форматирования?
Ответы
Ответ 1
Если вы хотите отступы в плане уровня вложенности, а не пробелов и вкладок, все становится сложным. Например, в следующем коде:
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
tokens. Модуль 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
Ответ 2
Да, это определенно возможно, вот рабочий пример:
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()
Ответ 3
Вы можете использовать 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
Если вы хотите, чтобы число уровней отступа было основано на линиях previouse с помощью :
, вы можете просто сделать это с небольшим изменением:
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]))
Ответ 4
>>> 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.
Ответ 5
Чтобы решить "настоящую" проблему, которая приведет к вашему вопросу, вы можете реализовать контекст-менеджер, который отслеживает уровень отступов и делает блок-структуру 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