Kann eine Zeile Python-Code die Verschachtelungsebene für Einrückungen kennen?

149

Von so etwas:

print(get_indentation_level())

    print(get_indentation_level())

        print(get_indentation_level())

Ich möchte so etwas bekommen:

1
2
3

Kann sich der Code auf diese Weise selbst lesen?

Ich möchte nur, dass die Ausgabe der stärker verschachtelten Teile des Codes stärker verschachtelt ist. Auf die gleiche Weise, wie dies das Lesen von Code erleichtert, würde dies das Lesen der Ausgabe erleichtern.

Natürlich könnte ich dies manuell implementieren, z. B. .format(), aber ich hatte eine benutzerdefinierte Druckfunktion im Sinn, print(i*' ' + string)bei der idie Einrückungsstufe angegeben wird. Dies wäre eine schnelle Möglichkeit, eine lesbare Ausgabe auf meinem Terminal zu erstellen.

Gibt es einen besseren Weg, um mühsame manuelle Formatierungen zu vermeiden?

Fab von Bellingshausen
quelle
70
Ich bin wirklich neugierig, warum du das brauchst.
Harrison
12
@ Harrison Ich wollte die Ausgabe meines Codes entsprechend der Einrückung im Code einrücken.
Fab von Bellingshausen
14
Die eigentliche Frage ist: Warum brauchen Sie das? Die Einrückungsstufe ist statisch. Sie wissen es mit Sicherheit, wenn Sie die get_indentation_level()Aussage in Ihren Code einfügen. Sie können genauso gut print(3)oder was auch immer direkt tun . Was vielleicht interessanter ist, ist die aktuelle Verschachtelungsebene auf dem Funktionsaufrufstapel.
tobias_k
19
Ist es zum Zweck des Debuggens Ihres Codes? Dies scheint entweder eine supergeniale Art zu sein, den Ausführungsfluss zu protokollieren, oder wie eine super überentwickelte Lösung für ein einfaches Problem, und ich bin mir nicht sicher, was es ist ... vielleicht beides!
Blackhawk
7
@FabvonBellingshausen: Das klingt so, als wäre es viel weniger lesbar als Sie hoffen. Ich denke, Sie könnten besser bedient werden depth, wenn Sie einen Parameter explizit weitergeben und ihm bei Bedarf den entsprechenden Wert hinzufügen, wenn Sie ihn an andere Funktionen übergeben. Die Verschachtelung Ihres Codes entspricht wahrscheinlich nicht genau dem Einzug, den Sie aus Ihrer Ausgabe heraus wünschen.
user2357112 unterstützt Monica

Antworten:

115

Wenn Sie Einrückungen in Bezug auf die Verschachtelungsebene anstelle von Leerzeichen und Tabulatoren wünschen, wird es schwierig. Zum Beispiel im folgenden Code:

if True:
    print(
get_nesting_level())

Der Aufruf von get_nesting_levelist tatsächlich eine Ebene tief verschachtelt, obwohl in der Zeile des get_nesting_levelAnrufs kein führendes Leerzeichen vorhanden ist . Inzwischen im folgenden Code:

print(1,
      2,
      get_nesting_level())

Der Aufruf von get_nesting_levelist trotz des Vorhandenseins führender Leerzeichen in seiner Zeile null Ebenen tief verschachtelt.

Im folgenden Code:

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

if True:
    print(get_nesting_level())

Die beiden Aufrufe von get_nesting_levelbefinden sich auf unterschiedlichen Verschachtelungsebenen, obwohl das führende Leerzeichen identisch ist.

Im folgenden Code:

if True: print(get_nesting_level())

Ist das verschachtelte Null-Ebenen oder Eins? In Bezug auf INDENTund DEDENTToken in der formalen Grammatik ist es null Stufen tief, aber Sie fühlen sich möglicherweise nicht so.


Wenn Sie dies tun möchten, müssen Sie die gesamte Datei bis zum Zeitpunkt des Aufrufs tokenisieren und zählen INDENTund DEDENTtoken. Das tokenizeModul wäre für eine solche Funktion sehr nützlich:

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
user2357112 unterstützt Monica
quelle
2
Dies funktioniert nicht so, wie ich es erwarten würde, wenn get_nesting_level()es innerhalb dieses Funktionsaufrufs aufgerufen wird. Es gibt die Verschachtelungsebene innerhalb dieser Funktion zurück. Könnte es umgeschrieben werden, um die 'globale' Verschachtelungsebene zurückzugeben?
Fab von Bellingshausen
11
@FabvonBellingshausen: Möglicherweise werden die Verschachtelungsebene für Einrückungen und die Verschachtelungsebene für Funktionsaufrufe verwechselt. Diese Funktion gibt die Verschachtelungsebene für Einrückungen an. Die Verschachtelungsebene für Funktionsaufrufe wäre ziemlich unterschiedlich und würde für alle meine Beispiele eine Stufe von 0 ergeben. Wenn Sie eine Art Hybrid auf Einrückungs- / Aufrufverschachtelungsebene wünschen, der sowohl für Funktionsaufrufe als auch für Kontrollflussstrukturen wie whileund inkrementiert withwird, ist dies machbar, aber nicht das, wonach Sie gefragt haben, und ändern Sie die Frage, um an dieser Stelle etwas anderes zu stellen wäre eine schlechte Idee.
user2357112 unterstützt Monica
38
Im Übrigen stimme ich allen Leuten zu, die sagen, dass dies eine wirklich seltsame Sache ist. Es gibt wahrscheinlich einen viel besseren Weg, um das Problem zu lösen, das Sie lösen möchten, und wenn Sie sich darauf verlassen, werden Sie wahrscheinlich gezwungen, alle Arten von bösen Hacks zu verwenden, um zu vermeiden, dass Sie Ihre Einrückung oder Funktionsaufrufstruktur ändern, wenn Sie dies tun müssen Änderungen an Ihrem Code.
user2357112 unterstützt Monica
4
Ich hatte sicherlich nicht erwartet, dass jemand dies tatsächlich beantwortet hätte. (Betrachten Sie das linecacheModul für solche Dinge - es wird zum Drucken von Tracebacks verwendet und kann Module verarbeiten, die aus Zip-Dateien und anderen seltsamen Importtricks importiert wurden)
Eevee
6
@Eevee: Ich hatte sicherlich nicht erwartet, dass so viele Leute dies befürworten würden ! linecacheVielleicht ist es gut, um die Anzahl der Datei-E / A zu reduzieren (und danke, dass Sie mich daran erinnert haben), aber wenn ich anfangen würde, dies zu optimieren, würde es mich stören, wie wir dieselbe Datei für Wiederholungen der Datei redundant neu tokenisieren gleicher Anruf oder für mehrere Anrufstellen innerhalb derselben Datei. Es gibt eine Reihe von Möglichkeiten, wie wir das auch optimieren können, aber ich bin mir nicht sicher, wie sehr ich dieses verrückte Ding wirklich stimmen und kugelsicher machen möchte.
user2357112 unterstützt Monica
22

Ja, das ist definitiv möglich, hier ist ein funktionierendes Beispiel:

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()
BPL
quelle
4
Kann dies in Bezug auf den Kommentar von @Prune gemacht werden, um die Einrückung in Ebenen anstelle von Leerzeichen zurückzugeben? Wird es immer in Ordnung sein, einfach durch 4 zu teilen?
Fab von Bellingshausen
2
Nein, durch 4 teilen, um die Einrückungsstufe zu erhalten, funktioniert mit diesem Code nicht. Kann überprüfen, indem der Einzug der letzten Druckanweisung erhöht wird. Der zuletzt gedruckte Wert wird nur erhöht.
Craig Burgler
10
Ein guter Anfang, beantwortet aber die Frage imo nicht wirklich. Die Anzahl der Leerzeichen entspricht nicht der Einrückungsstufe.
wim
1
Es ist nicht so einfach. Das Ersetzen von 4 Leerzeichen durch einzelne Leerzeichen kann die Logik des Codes ändern.
wim
1
Dieser Code ist jedoch perfekt für das, wonach das OP gesucht hat: (OP-Kommentar Nr. 9): "Ich wollte die Ausgabe meines Codes entsprechend der Einrückung im Code einrücken." Also kann er so etwas tunprint('{Space}'*get_indentation_level(), x)
Craig Burgler
10

Sie können verwenden sys.current_frame.f_lineno, um die Zeilennummer zu erhalten. Um die Anzahl der Einrückungsstufen zu ermitteln, müssen Sie die vorherige Zeile mit der Einrückung Null suchen und dann die aktuelle Zeilennummer von der Nummer dieser Zeile abziehen. Sie erhalten die Anzahl der Einrückungen:

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

Demo:

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

Wenn Sie die Nummer der Einrückungsstufe basierend auf den vorherigen Zeilen mit möchten :, können Sie dies einfach mit einer kleinen Änderung tun:

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

Demo:

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

Als alternative Antwort gibt es hier eine Funktion zum Abrufen der Anzahl der Einrückungen (Leerzeichen):

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]))
Kasramvd
quelle
Frage nach Anzahl der Einrückungsstufen, nicht nach Anzahl der Leerzeichen. Sie sind nicht unbedingt proportional.
wim
Für Ihren Demo-Code sollte die Ausgabe 1 - 2 - 3 - 3
Craig Burgler
@CraigBurgler Um 1 - 2 - 3 - 3 zu erhalten, können wir die Anzahl der Zeilen vor der aktuellen Zeile zählen, die mit a enden, :bis wir auf die Zeile mit null Einrückung stoßen. Schauen Sie sich die Bearbeitung an!
Kasramvd
2
hmmm ... ok ... jetzt probiere einige der Testfälle von @ user2357112 aus;)
Craig Burgler
@CraigBurgler Diese Lösung ist nur für die meisten Fälle geeignet. In Bezug auf diese Antwort gibt es auch einen allgemeinen Weg, und es gibt auch keine umfassende Lösung. Versuchen Sie{3:4, \n 2:get_ind_num()}
Kasramvd
7

Um das "echte" Problem zu lösen, das zu Ihrer Frage führt, können Sie einen Kontextmanager implementieren, der die Einrückungsstufe verfolgt und die withBlockstruktur im Code den Einrückungsstufen der Ausgabe entspricht. Auf diese Weise spiegelt der Codeeinzug immer noch den Ausgabeeinzug wider, ohne beide zu stark zu koppeln. Es ist weiterhin möglich, den Code in verschiedene Funktionen umzugestalten und andere Einrückungen basierend auf der Codestruktur zu verwenden, die nicht mit der Ausgabeeinrückung in Konflikt geraten.

#!/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()

Ausgabe:

0
  1
    Hallo 2
      3
    and back one level 2
BlackJack
quelle
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.
Craig Burgler
quelle
12
Dies gibt die Einrückung in Leerzeichen, nicht in Ebenen. Wenn der Programmierer keine konsistenten Einrückungsbeträge verwendet, kann die Konvertierung in Ebenen hässlich sein.
Beschneiden Sie den
4
Ist die Funktion nicht dokumentiert? Ich kann es hier
GingerPlusPlus