web-dev-qa-db-ja.com

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

このようなものから:

print(get_indentation_level())

    print(get_indentation_level())

        print(get_indentation_level())

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

1
2
3

コードはこのようにして自分自身を読み取ることができますか?

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

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

骨の折れる手作業による書式設定を回避する、これを行うより良い方法はありますか?

147

スペースやタブではなく、ネストレベルの観点からインデントが必要な場合は、注意が必要です。たとえば、次のコードでは:

if True:
    print(
get_nesting_level())

get_nesting_level呼び出しの行に先行する空白がないという事実にもかかわらず、get_nesting_levelの呼び出しは実際には1レベル深くネストされています。一方、次のコードでは:

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の2つの呼び出しは、先頭の空白が同一であるにもかかわらず、異なるネストレベルにあります。

次のコードでは:

if True: print(get_nesting_level())

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


これを行うには、呼び出しの時点までファイル全体をトークン化し、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
113
user2357112

ええ、それは間違いなく可能です、ここに実例があります:

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

行番号を取得するには、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]))
9
Kasrâmvd

質問につながる「実際の」問題を解決するには、インデントレベルを追跡し、コードのwithブロック構造を出力のインデントレベルに対応させるcontextmanagerを実装します。このように、コードのインデントは、出力のインデントを反映しますが、両方をあまり結合しません。コードをさまざまな関数にリファクタリングしたり、出力インデントを台無しにしないコード構造に基づいて他のインデントを設定することも可能です。

#!/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 __== '__main__':
    main()

出力:

0
  1
    Hallo 2
      3
    and back one level 2
7
BlackJack
>>> 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.
6
Craig Burgler