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


149

从这样的事情:

print(get_indentation_level())

    print(get_indentation_level())

        print(get_indentation_level())

我想得到这样的东西:

1
2
3

代码可以这样读取吗?

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

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

有没有更好的方法可以避免麻烦的手动格式化?


70
我真的很好奇你为什么需要这个。
哈里森

12
@Harrison我想根据代码的缩进方式缩进代码的输出。
Fab von Bellingshausen,

14
真正的问题是:您为什么需要这个?压痕级别是静态的;当您将get_indentation_level()语句放入代码中时,您肯定会知道。您既可以做,print(3)也可以直接做。更令人讨厌的是函数调用堆栈上的当前嵌套级别。
tobias_k

19
是为了调试代码?这似乎是一种记录执行流程的超级天才方法,或者就像是针对一个简单问题的过度设计的超级解决方案,我不确定这是什么……也许两者都有!
黑鹰

7
@FabvonBellingshausen:听起来它的可读性比您期望的要差得多。我认为最好通过显式传递depth参数并在将其传递给其他函数时根据需要向其添加适当的值来更好地为您服务。代码的嵌套不太可能与您要从输出中缩进的部分完全对应。
user2357112支持Monica 2016年

Answers:


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

是嵌套的零级,还是一级?在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

2
这与我get_nesting_level()在该函数调用中调用when 所期望的方式不一样-它返回该函数内的嵌套级别。是否可以重写它以返回“全局”嵌套级别?
Fab von Bellingshausen,

11
@FabvonBellingshausen:您可能将缩进嵌套级别和函数调用嵌套级别混为一谈。此功能提供缩进嵌套级别。函数调用嵌套级别将有很大的不同,在我所有的示例中,其级别均为0。如果您想要一种缩进/调用嵌套级别的混合函数,并且对于函数调用和控制流结构(例如while和)都可以增加with,那将是可行的,但这不是您所要的,因此将问题更改为提出不同的要求将是一个坏主意。
user2357112支持Monica

38
顺便说一句,我完全同意所有人所说的这是一件很奇怪的事情。可能有更好的方法来解决您要解决的任何问题,依靠它可能会迫使您使用各种讨厌的技巧来避免在需要时更改缩进或函数调用结构,从而使您筋疲力尽更改您的代码。
user2357112支持Monica

4
我当然没想到有人会真正回答这个问题。(不过,请考虑将linecache模块用于此类事情-它用于打印回溯,并且可以处理从zip文件和其他奇怪的导入技巧导入的模块)
Eevee

6
@Eevee:我当然没想到这么多人来给予好评这个!linecache也许可以减少文件I / O的数量(并感谢我提醒我),但是如果我开始对其进行优化,那么我将为重复重复为同一文件冗余地加令牌而感到困扰。同一呼叫,或同一文件中的多个呼叫站点。我们也可以通过多种方法来优化它,但是我不确定我到底想调优和防弹这疯狂的事情。
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()

4
关于@Prune的注释,是否可以按级别而不是空格返回缩进?简单地除以4总是可以的吗?
Fab von Bellingshausen,

2
不,除以4以获得缩进级别将不适用于此代码。可以通过增加最后打印语句的缩进程度来验证,最后打印值只是增加了。
Craig Burgler '16

10
一个好的开始,但是并不能真正回答imo这个问题。空格数与缩进级别不同。
2013年

1
这不是那么简单。用单个空格替换4个空格可以更改代码的逻辑。
wim 2016年

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

问题要求输入缩进级别数,而不是空格数。它们不一定成比例。
wim 2016年

对于您的演示代码,输出应为
1-2-3-3

@CraigBurgler要获得1-2-3-3,我们可以计算当前行之前以a结尾的行数,:直到遇到缩进为零的行,请检查编辑!
Kasramvd

2
嗯...好吧...现在尝试一些@ user2357112的测试用例;)
Craig Burgler's

@CraigBurgler此解决方案仅适用于大多数情况,关于该答案,这也是一种通用方法,也未提供全面的解决方案。试试{3:4, \n 2:get_ind_num()}
Kasramvd

7

为了解决导致您提出问题的“实际”问题,您可以实现一个contextmanager,它可以跟踪缩进级别并使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,2016年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.