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

Posted

技术标签:

【中文标题】一行 Python 代码能知道它的缩进嵌套级别吗?【英文标题】:Can a line of Python code know its indentation nesting level? 【发布时间】:2017-01-03 11:32:48 【问题描述】:

从这样的事情:

print(get_indentation_level())

    print(get_indentation_level())

        print(get_indentation_level())

我想得到这样的东西:

1
2
3

代码可以这样读取自己吗?

我想要的只是更多嵌套的代码部分的输出。就像这使代码更易于阅读一样,它会使输出更易于阅读。

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

有没有更好的方法来避免繁琐的手动格式化?

【问题讨论】:

我真的很好奇你为什么需要这个。 @Harrison 我想根据代码中的缩进方式缩进我的代码输出。 真正的问题是:你为什么需要这个?缩进级别是静态的;当您将 get_indentation_level() 语句放入您的代码时,您肯定会知道这一点。您也可以直接执行print(3) 或其他任何操作。可能更有趣的是函数调用堆栈上的当前嵌套级别。 是为了调试你的代码吗?这看起来要么是记录执行流程的超级天才方式,要么是针对简单问题的超级过度设计的解决方案,我不确定它是什么......也许两者兼而有之! @FabvonBellingshausen:听起来它的可读性比您希望的要低得多。我认为通过显式传递 depth 参数并在将其传递给其他函数时根据需要为其添加适当的值可能会更好。代码的嵌套不太可能与您希望输出的缩进完全对应。 【参考方案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())

是嵌套的零层还是一层?就形式语法中的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

【讨论】:

当在该函数调用中调用get_nesting_level() 时,这不会像我期望的那样工作——它返回该函数中的嵌套级别。是否可以重写以返回“全局”嵌套级别? @FabvonBellingshausen:您可能混淆了缩进嵌套级别和函数调用嵌套级别。此函数提供缩进嵌套级别。函数调用嵌套级别会相当不同,并且对于我的所有示例都会给出级别 0。如果您想要一种缩进/调用嵌套级别的混合,它可以为函数调用和控制流结构(如whilewith)递增,那是可行的,但这不是您所要求的,并将问题更改为在这一点上问一些不同的东西是个坏主意。 顺便说一句,我完全同意所有人的看法,他们说这是一件非常奇怪的事情。可能有更好的方法来解决您要解决的任何问题,并且依靠它可能会通过迫使您使用各种讨厌的技巧来避免在需要时更改缩进或函数调用结构来束缚您更改您的代码。 我当然没想到有人会真正回答这个问题。 (尽管考虑使用 linecache 模块来处理类似的事情——它用于打印回溯,并且可以处理从 zip 文件导入的模块和其他奇怪的导入技巧) @Eevee:我当然没想到会有这么多人支持这个! linecache 可能有利于减少文件 I/O 的数量(感谢您提醒我),但如果我开始优化它,我会为我们如何冗余地重新标记同一个文件而烦恼重复同一呼叫,或同一文件中的多个呼叫站点。我们也可以通过多种方式对其进行优化,但我不确定我到底有多想调整和防弹这个疯狂的东西。【参考方案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()

【讨论】:

关于@Prune 发表的评论,可以这样做以返回级别而不是空格的缩进吗?简单地除以 4 总是可以的吗? 不,除以 4 来获得缩进级别不适用于此代码。可以通过增加上次打印语句的缩进级别来验证,上次打印的值只是增加了。 一个好的开始,但并没有真正回答 imo 的问题。空格数与缩进级别不同。 没那么简单。用单个空格替换4个空格可以改变代码的逻辑。 但是这段代码非常适合 OP 正在寻找的内容:(OP 评论 #9):“我想根据代码中的缩进方式缩进我的代码的输出。”所以他可以做类似print('Space'*get_indentation_level(), x)【参考方案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

如果您想要基于: 前面行的缩进级别的数量,您只需稍作改动即可:

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

【讨论】:

问题要求缩进级别数,而不是空格数。它们不一定成比例。 对于您的演示代码,输出应为 1 - 2 - 3 - 3 @CraigBurgler 为了获得 1 - 2 - 3 - 3,我们可以计算当前行之前以 : 结尾的行数,直到遇到缩进为零的行,查看编辑! hmmm ...好的...现在尝试一些@user2357112的测试用例;) @CraigBurgler 该解决方案仅适用于大多数情况,关于该答案,它也是一种通用方法,它也没有提供全面的解决方案。试试3:4, \n 2:get_ind_num()【参考方案4】:

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

【讨论】:

【参考方案5】:
>>> 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.

【讨论】:

这给出了空格而不是级别的缩进。除非程序员使用一致的缩进量,否则转换为级别可能很难看。 该功能是否未记录?我找不到它here

以上是关于一行 Python 代码能知道它的缩进嵌套级别吗?的主要内容,如果未能解决你的问题,请参考以下文章

熟悉快捷键,提高开发效率,Python自带的IDLE常用快捷键汇总

python3 中,一个语句可以分成多行书写吗

Python 条件语句学习

编译Python代码时unindent不匹配任何外部缩进级别错误

Python中的空格和缩进问题总结

Python的学习之-流程控制