Pythonコードの行は、インデントのネストレベルを知ることができますか?


149

このようなものから:

print(get_indentation_level())

    print(get_indentation_level())

        print(get_indentation_level())

私はこのようなものを手に入れたいです:

1
2
3

この方法でコードを読み取ることができますか?

私が欲しいのは、コードのよりネストされた部分からの出力がよりネストされることです。これによりコードが読みやすくなるのと同じように、出力も読みやすくなります。

もちろん、たとえばを使用してこれを手動で実装することもできます.format()が、私が念頭に置いていたのは、インデントレベルがprint(i*' ' + string)どこにあるかというカスタムの印刷関数でしたi。これは、私の端末で読みやすい出力を作成する簡単な方法です。

これを行うための手作業でのフォーマットの手間を省くより良い方法はありますか?


70
なぜこれが必要なのか本当に知りたいです。
ハリソン

12
@Harrisonコードでのインデント方法に従って、コードの出力をインデントしたいと思いました。
Fab von Bellingshausen

14
本当の質問は:なぜこれが必要なのか?インデントレベルは静的です。get_indentation_level()ステートメントをコードに入れると、確実にそれを知ることができます。あなたはちょうど同じようにprint(3)何でも直接することができます。より複雑になる可能性があるのは、関数呼び出しスタックでのネストの現在のレベルです。
tobias_k

19
コードのデバッグを目的としていますか?これは、実行のフローをログに記録するための非常に天才的な方法のようであるか、単純な問題に対する非常に高度に設計された解決策のようです。
ブラックホーク

7
@FabvonBellingshausen:それはあなたが期待しているよりもずっと読みにくくなるように思えます。depthパラメータを明示的に渡し、他の関数に渡すときに必要に応じて適切な値を追加すると、より良い結果が得られると思います。コードのネストは、出力に必要なインデントに完全に対応しているとは限りません。
user2357112は、モニカの2016

回答:


115

スペースやタブではなく入れ子のレベルでインデントを設定したい場合は、注意が必要です。たとえば、次のコードでは:

if True:
    print(
get_nesting_level())

への呼び出しget_nesting_levelは、呼び出しの行に先行空白がないにもかかわらず、実際には1レベルの深さにネストされてい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())

への2つの呼び出しget_nesting_levelは、先頭の空白が同一であるにもかかわらず、ネストレベルが異なります。

次のコードでは:

if True: print(get_nesting_level())

ネストされたゼロレベルですか、それとも1つですか?正式な文法のトークンに関してはINDENTDEDENT深さはゼロレベルですが、同じように感じるとは限りません。


これを行う場合は、呼び出しとカウント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

2
これはget_nesting_level()、関数呼び出し内でが呼び出されたときに期待する方法では機能しません。その関数内のネストレベルを返します。「グローバル」な入れ子レベルを返すように書き換えることはできますか?
Fab von Bellingshausen

11
@FabvonBellingshausen:インデントのネストレベルと関数呼び出しのネストレベルが混同されている可能性があります。この関数は、インデントのネストレベルを提供します。関数呼び出しのネストレベルはかなり異なり、すべての例でレベル0になります。あなたはインデントの並べ替えをしたい場合は/のように、両方の関数呼び出しと制御フロー構造のための刻みというネストレベルのハイブリッドを呼び出すwhileと、withなんとかなるだろう、が、それはあなたが尋ねたものではないのですが、この時点で何か別のを依頼する質問を変更悪い考えでしょう。
user2357112は、モニカの

38
ちなみに、これは本当に奇妙なことだと私はすべての人々と完全に同意しています。おそらく、解決しようとしている問題を解決するはるかに優れた方法があり、これに依存することで、あらゆる種類の厄介なハックを使用して、必要なときにインデントや関数呼び出し構造を変更しないように強制することができます。コードの変更。
user2357112は、モニカの

4
私は確かに誰かが実際にこれに答えたことを期待していませんでした。(linecacheただし、このようなものについてはモジュールを検討してください—トレースバックを印刷するために使用され、zipファイルからインポートされたモジュールおよびその他の奇妙なインポートトリックを処理できます)
Eevee

6
@Eevee:私は確かにするために非常に多くの人々を期待していなかったupvoteこれを!linecacheファイルI / Oの量を減らすのには良いかもしれません(そしてそれを思い出させてくれてありがとう)が、それを最適化し始めた場合、同じファイルを繰り返し再トークン化して冗長化する方法に悩まされるでしょう。同じ通話、または同じファイル内の複数の通話サイト。それを最適化する方法もいくつかありますが、このクレイジーなものを本当にどれだけ調整し、防弾したいのかわかりません。
user2357112は2016

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で除算すると、このコードでは機能しません。最後の印刷ステートメントのインデントのレベルを上げることで確認でき、最後に印刷された値が増加するだけです。
クレイグバーガー

10
良いスタートですが、質問imoには本当に答えません。スペースの数はインデントレベルと同じではありません。
2016

1
それはそれほど単純ではありません。4つのスペースを1つのスペースに置き換えると、コードのロジックが変わる可能性があります。
2016

1
しかし、このコードは、OPが探していたものに最適です。したがって、彼は次のようなことを行うことができますprint('{Space}'*get_indentation_level(), x)
Craig Burgler '26

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

スペースの数ではなく、インデントレベルの数を尋ねる質問。それらは必ずしも比例しているわけではありません。
2016

デモコードの場合、出力は
1-2-3-3になります

@CraigBurgler 1-2-3-3を取得するために、現在の行の前にある、:インデントが0の行に到達するまでの行数をカウントできます。編集をチェックしてください!
Kasramvd

2
hmmm ... ok ...今すぐ@ user2357112のテストケースのいくつかを試してください;)
Craig

@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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.