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

Fab*_*sen 146 python reflection metaprogramming indentation tokenize

从这样的事情:

print(get_indentation_level())

    print(get_indentation_level())

        print(get_indentation_level())
Run Code Online (Sandbox Code Playgroud)

我想得到这样的东西:

1
2
3
Run Code Online (Sandbox Code Playgroud)

代码可以用这种方式读取自己吗?

我想要的是更嵌套的代码嵌套部分的输出.与使代码更易于阅读的方式相同,它将使输出更易于阅读.

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

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

use*_*ica 114

如果你想在嵌套级别而不是空格和制表符方面进行缩进,那么事情会变得棘手.例如,在以下代码中:

if True:
    print(
get_nesting_level())
Run Code Online (Sandbox Code Playgroud)

get_nesting_level尽管get_nesting_level调用行上没有前导空格,但调用实际上嵌套了一层深度.同时,在以下代码中:

print(1,
      2,
      get_nesting_level())
Run Code Online (Sandbox Code Playgroud)

get_nesting_level尽管在其行上存在前导空格,但调用是嵌套的零级深度.

在以下代码中:

if True:
  if True:
    print(get_nesting_level())

if True:
    print(get_nesting_level())
Run Code Online (Sandbox Code Playgroud)

get_nesting_level尽管领先的空白是相同的,但两次调用处于不同的嵌套级别.

在以下代码中:

if True: print(get_nesting_level())
Run Code Online (Sandbox Code Playgroud)

是嵌套的零级别,还是一个?在形式语法方面,它的标准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
Run Code Online (Sandbox Code Playgroud)

  • 顺便说一句,我完全同意所有人说这是一件非常奇怪的事情.可能有一个更好的方法来解决你试图解决的任何问题,并且依赖于这可能会迫使你使用各种讨厌的黑客来避免在需要时改变你的缩进或函数调用结构.更改代码. (38认同)
  • @FabvonBellingshausen:您可能会将缩进嵌套级别和函数调用嵌套级别混淆.此函数提供缩进嵌套级别.函数调用嵌套级别会有很大的不同,并且对于我的所有示例都会给出0级别.如果你想要一种缩进/调用嵌套级别混合,它为函数调用和控制流结构(例如`while`和`with`)递增,那是可行的,但它不是你要求的,并且将问题改为在这一点上问一些不同的东西会是一个坏主意. (11认同)
  • @Eevee:我当然没想到会有这么多人*赞成这个!`linecache`可能有助于减少文件I/O的数量(并且感谢提醒我),但是如果我开始优化它,我会被我们如何冗余地重新标记同一个文件所困扰重复相同的呼叫,或同一文件中的多个呼叫站点.我们可以通过多种方式对其进行优化,但我不确定我真的想要调整和防止这种疯狂的事情. (6认同)
  • 我当然没想到有人真的回答过这个问题.(考虑`linecache`模块这样的东西虽然 - 它用于打印回溯,并且可以处理从zip文件导入的模块和其他奇怪的导入技巧) (4认同)
  • 当在该函数调用中调用`get_nesting_level()`时,这不会以我期望的方式工作 - 它返回该函数内的嵌套级别.是否可以重写以返回"全局"嵌套级别? (2认同)

BPL*_*BPL 21

是的,这绝对是可能的,这是一个有效的例子:

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()
Run Code Online (Sandbox Code Playgroud)

  • 一个好的开始,但并没有真正回答imo的问题.空格的数量与缩进级别不同. (10认同)
  • 关于@Prune的评论,是否可以在级别而不是空格中返回缩进?简单地除以4总是可以的吗? (4认同)
  • @wim但是OP接受了答案0_O! (4认同)
  • 不,除以4以获得缩进级别将无法使用此代码.可以通过增加最后一个打印语句的缩进级别来验证,最后的打印值只是增加了. (2认同)

Kas*_*mvd 9

您可以使用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
Run Code Online (Sandbox Code Playgroud)

演示:

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
Run Code Online (Sandbox Code Playgroud)

如果你想要基于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(':'))
Run Code Online (Sandbox Code Playgroud)

演示:

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
Run Code Online (Sandbox Code Playgroud)

作为替代答案,这里是一个获取缩进数(空格)的函数:

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]))
Run Code Online (Sandbox Code Playgroud)

  • 嗯...好的...现在试试@ user2357112的一些测试用例;) (2认同)

Bla*_*ack 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()
Run Code Online (Sandbox Code Playgroud)

输出:

0
  1
    Hallo 2
      3
    and back one level 2
Run Code Online (Sandbox Code Playgroud)


Cra*_*ler 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.
Run Code Online (Sandbox Code Playgroud)

  • 这给出了空格中的缩进,而不是级别.除非程序员使用一致的缩进量,否则转换为级别可能会很难看. (12认同)
  • 功能是否未记录?我找不到它[这里](https://docs.python.org/3/library/inspect.html) (4认同)