Может ли строка кода Python знать свой уровень вложенности отступа?

149

Из чего-то вроде этого:

print(get_indentation_level())

    print(get_indentation_level())

        print(get_indentation_level())

Я хотел бы получить что-то вроде этого:

1
2
3

Может ли код читать себя таким образом?

Все, что я хочу, - это вывод из более вложенных частей кода, чтобы быть более вложенными. Точно так же, как это облегчает чтение кода, облегчает чтение вывода.

Конечно, я мог бы реализовать это вручную, используя, например .format(), но я имел в виду пользовательскую функцию печати, print(i*' ' + string)где iбудет уровень отступа. Это был бы быстрый способ сделать читаемый вывод на моем терминале.

Есть ли лучший способ сделать это, чтобы избежать кропотливого ручного форматирования?

Фаб фон Беллинсгаузен
источник
71
Мне действительно любопытно, зачем тебе это нужно.
Харрисон
13
@ Харрисон Я хотел сделать отступ в выводе моего кода в соответствии с тем, как он был вставлен в код.
Фаб фон Беллинсгаузен
14
Реальный вопрос: зачем вам это нужно? Уровень отступа является статическим; Вы знаете это с уверенностью, когда вставляете это утверждение get_indentation_level()в свой код. Вы можете так же хорошо делать print(3)или что угодно напрямую. Что может быть более интересным, так это текущий уровень вложенности в стек вызовов функций.
tobias_k
19
Это для отладки вашего кода? Это кажется либо супер-гениальным способом регистрации потока выполнения, либо как супер-слишком сложное решение для простой проблемы, и я не уверен, что это ... может быть, и то и другое!
Блэкхок
7
@FabvonBellingshausen: Похоже, что это будет гораздо менее читабельным, чем вы надеетесь. Я думаю, что вам лучше обойтись, явно передавая depthпараметр и добавляя к нему соответствующее значение по мере необходимости, когда вы передаете его другим функциям. Вложенность вашего кода, скорее всего, не будет точно соответствовать отступу, который вы хотите получить из вашего вывода.
user2357112 поддерживает Монику

Ответы:

115

Если вы хотите сделать отступ с точки зрения уровня вложенности, а не пробелов и табуляций, все будет сложно. Например, в следующем коде:

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
user2357112 поддерживает Monica
источник
2
Это не работает так, как я ожидаю, когда get_nesting_level()вызывается в этом вызове функции, это возвращает уровень вложенности в этой функции. Можно ли его переписать, чтобы вернуть «глобальный» уровень вложенности?
Фаб фон Беллинсгаузен
11
@FabvonBellingshausen: Возможно, вы перепутали уровень вложенности отступов и уровень вложенности вызовов функций. Эта функция дает уровень вложенности отступа. Уровень вложенности вызова функции будет довольно разным и даст уровень 0 для всех моих примеров. Если вам нужен своего рода гибрид уровня вложенности / отступа вызовов, который увеличивается как для вызовов функций, так и для структур управления потоками, таких как whileи with, это было бы выполнимо, но это не то, о чем вы просили, и измените вопрос, чтобы задать что-то другое на этом этапе. было бы плохой идеей.
user2357112 поддерживает Монику
38
Кстати, я полностью согласен со всеми, кто говорит, что это действительно странная вещь. Вероятно, есть гораздо лучший способ решить любую проблему, которую вы пытаетесь решить, и полагаться на это, вероятно, затруднит вас, заставив вас использовать все виды мерзких хаков, чтобы избежать изменения отступа или структуры вызова функции, когда вам нужно сделать изменения в вашем коде.
user2357112 поддерживает Монику
4
я, конечно, не ожидал, что кто-то на самом деле ответил на это. (рассмотрите linecacheмодуль для подобных вещей - он используется для печати трассировок и может обрабатывать модули, импортированные из zip-файлов и других странных приемов импорта)
Eevee
6
@Eevee: Я, конечно, не ожидал, что так много людей поддержат это! linecacheможет быть полезно для уменьшения количества файловых операций ввода-вывода (и спасибо, что напомнили мне об этом), но если бы я начал оптимизировать это, меня бы беспокоило, как мы избыточно повторно токенизируем один и тот же файл для повторений один и тот же вызов или для нескольких сайтов вызовов в одном файле. Есть несколько способов, которыми мы могли бы оптимизировать это тоже, но я не уверен, насколько я действительно хочу настроить и пуленепробиваемую эту сумасшедшую вещь.
user2357112 поддерживает Monica
22

Да, это определенно возможно, вот рабочий пример:

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()
BPL
источник
4
Относительно комментария, сделанного @Prune, можно ли сделать это, чтобы вернуть отступ в уровнях вместо пробелов? Всегда ли будет нормально просто разделить на 4?
Фаб фон Беллинсгаузен
2
Нет, деление на 4, чтобы получить уровень отступа, не будет работать с этим кодом. Можно проверить, увеличив уровень отступа последнего оператора печати, последнее напечатанное значение только увеличивается.
Крейг Бурглер
10
Хорошее начало, но на самом деле не отвечает на вопрос imo. Количество пробелов не совпадает с уровнем отступа.
Вим
1
Это не так просто. Замена 4 пробелов одинарными пробелами может изменить логику кода.
Вим
1
Но этот код идеально подходит для того, что искал OP: (комментарий OP # 9): «Я хотел сделать отступ в выводе моего кода в соответствии с тем, как он был вставлен в код». Таким образом, он может сделать что-то вродеprint('{Space}'*get_indentation_level(), x)
Крейг Бурглер
10

Вы можете использовать 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]))
Kasramvd
источник
задается вопрос о количестве уровней отступа, а не о количестве пробелов. они не обязательно пропорциональны.
Вим
Для вашего демо-кода вывод должен быть 1 - 2 - 3 - 3
Крейг Бурглер
@CraigBurgler Для получения 1 - 2 - 3 - 3 мы можем посчитать количество строк до текущей строки, которые заканчиваются на, :пока мы не встретим строку с нулевым отступом. Проверьте редактирование!
Касрамвд
2
хммм ... хорошо ... теперь попробуйте некоторые из тестовых примеров @ user2357112;)
Крейг Бурглер
@CraigBurgler Это решение предназначено только для большинства случаев. Что касается ответа, он также является общим, и он не дает исчерпывающего решения. Попробуйте{3:4, \n 2:get_ind_num()}
Kasramvd
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
Блэк Джек
источник
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.
Крейг Бурглер
источник
12
Это дает отступ в пространствах, а не в уровнях. Если программист не использует согласованные суммы отступов, это может быть уродливо для преобразования в уровни.
чернослив
4
Функция недокументирована? Я не могу найти его здесь
GingerPlusPlus