Czy wiersz kodu Pythona może znać poziom zagnieżdżenia wcięć?

149

Z czegoś takiego:

print(get_indentation_level())

    print(get_indentation_level())

        print(get_indentation_level())

Chciałbym dostać coś takiego:

1
2
3

Czy kod może się w ten sposób odczytać?

Chcę tylko, aby dane wyjściowe z bardziej zagnieżdżonych części kodu były bardziej zagnieżdżone. W ten sam sposób, w jaki ułatwia to odczytanie kodu, ułatwiłoby to odczytanie danych wyjściowych.

Oczywiście mogę zaimplementować to ręcznie, za pomocą np .format(), ale to, co miał na myśli, był zwyczaj Funkcja drukowania co print(i*' ' + string)gdzie ijest poziom wcięcia. Byłby to szybki sposób na uzyskanie czytelnego wyniku na moim terminalu.

Czy istnieje lepszy sposób na to, aby uniknąć żmudnego ręcznego formatowania?

Fab von Bellingshausen
źródło
70
Jestem naprawdę ciekawy, dlaczego tego potrzebujesz.
Harrison
12
@Harrison Chciałem wciąć dane wyjściowe mojego kodu zgodnie z wcięciem w kodzie.
Fab von Bellingshausen
14
Prawdziwe pytanie brzmi: dlaczego miałbyś tego potrzebować? Poziom wcięcia jest statyczny; wiesz to z całą pewnością, kiedy umieszczasz tę get_indentation_level()instrukcję w swoim kodzie. Możesz równie dobrze zrobić print(3)lub cokolwiek bezpośrednio. Bardziej interesujący może być obecny poziom zagnieżdżenia na stosie wywołań funkcji.
tobias_k
19
Czy ma to na celu debugowanie kodu? Wydaje się to albo super genialnym sposobem rejestrowania przepływu wykonywania, albo bardzo rozbudowanym rozwiązaniem prostego problemu i nie jestem pewien, który to jest ... może jedno i drugie!
Blackhawk
7
@FabvonBellingshausen: Wygląda na to, że byłoby o wiele mniej czytelne, niż się spodziewasz. Myślę, że możesz być lepiej obsługiwany przez jawne przekazanie depthparametru i dodanie do niego odpowiedniej wartości w razie potrzeby, gdy przekazujesz go do innych funkcji. Zagnieżdżenie kodu prawdopodobnie nie będzie dokładnie odpowiadać wcięciom, które chcesz w wyniku.
user2357112 obsługuje Monikę

Odpowiedzi:

115

Jeśli chcesz wcięcia pod względem poziomu zagnieżdżenia, a nie spacji i tabulatorów, sprawy stają się trudne. Na przykład w poniższym kodzie:

if True:
    print(
get_nesting_level())

wywołanie get_nesting_leveljest faktycznie zagnieżdżone o jeden poziom w głąb, pomimo faktu, że w wierszu get_nesting_levelwywołania nie ma wiodących białych znaków . Tymczasem w poniższym kodzie:

print(1,
      2,
      get_nesting_level())

wywołanie get_nesting_leveljest zagnieżdżone w głębi zera, pomimo obecności wiodących białych znaków w jego linii.

W poniższym kodzie:

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

if True:
    print(get_nesting_level())

te dwa wywołania get_nesting_levelznajdują się na różnych poziomach zagnieżdżenia, mimo że wiodące białe znaki są identyczne.

W poniższym kodzie:

if True: print(get_nesting_level())

czy to zagnieżdżone poziomy zerowe czy jeden? Jeśli chodzi o INDENTi DEDENTtokeny w gramatyce formalnej, jest to poziom zerowy, ale możesz nie czuć tego samego.


Jeśli chcesz to zrobić, będziesz musiał tokenizować cały plik do momentu wywołania, liczenia INDENTi DEDENTtokenów. tokenizeModuł byłby bardzo przydatny dla takiej funkcji:

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 obsługuje Monikę
źródło
2
Nie działa to w sposób, jakiego oczekiwałbym, gdy get_nesting_level()jest wywoływana w ramach tego wywołania funkcji - zwraca poziom zagnieżdżenia w ramach tej funkcji. Czy można go przepisać, aby przywrócić „globalny” poziom zagnieżdżenia?
Fab von Bellingshausen
11
@FabvonBellingshausen: Możesz pomieszać poziom zagnieżdżenia wcięć i poziom zagnieżdżenia wywołań funkcji. Ta funkcja określa poziom zagnieżdżenia wcięć. Poziom zagnieżdżenia wywołań funkcji byłby raczej inny i dałby poziom 0 dla wszystkich moich przykładów. Jeśli potrzebujesz czegoś w rodzaju hybrydy poziomu zagnieżdżenia wcięć / wywołań, która zwiększa się zarówno dla wywołań funkcji, jak i struktur przepływu sterowania, takich jak whilei with, byłoby to wykonalne, ale nie o to prosiłeś i zmieniając pytanie, aby zadać coś innego w tym momencie byłby złym pomysłem.
user2357112 obsługuje Monikę
38
Nawiasem mówiąc, całkowicie zgadzam się ze wszystkimi ludźmi, którzy twierdzą, że to naprawdę dziwna rzecz. Prawdopodobnie istnieje znacznie lepszy sposób rozwiązania dowolnego problemu, który próbujesz rozwiązać, a poleganie na tym może spowodować ścięcie ścięgna, zmuszając cię do używania wszelkiego rodzaju paskudnych hacków, aby uniknąć zmiany wcięcia lub struktury wywołania funkcji, gdy musisz to zrobić zmiany w Twoim kodzie.
user2357112 obsługuje Monikę
4
z pewnością nie spodziewałem się, że ktoś rzeczywiście na to odpowiedział. (rozważ linecachemoduł do takich rzeczy - jest używany do drukowania
Eevee
6
@Eevee: Z pewnością nie spodziewałem się, że tak wielu ludzi to poprze ! linecachemoże przydać się do zmniejszenia ilości operacji we / wy pliku (i dziękuję za przypomnienie mi o tym), ale gdybym zaczął to optymalizować, przejmowałbym się tym, jak ponownie tokenizujemy ten sam plik w celu powtórzenia to samo połączenie lub wiele witryn z połączeniami w tym samym pliku. Jest wiele sposobów, w jakie moglibyśmy to zoptymalizować, ale nie jestem pewien, jak bardzo chcę dostroić i zabezpieczyć tę szaloną rzecz.
user2357112 obsługuje Monikę
22

Tak, to zdecydowanie możliwe, oto działający przykład:

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
źródło
4
Odnosząc się do komentarza @Prune, czy można sprawić, by zwracane było wcięcie na poziomach zamiast spacji? Czy zawsze będzie dobrze po prostu podzielić przez 4?
Fab von Bellingshausen
2
Nie, podziel przez 4, aby uzyskać poziom wcięcia, nie zadziała z tym kodem. Potrafi zweryfikować zwiększając poziom wcięcia ostatniej wypisanej instrukcji, ostatnia drukowana wartość jest właśnie zwiększana.
Craig Burgler
10
Dobry początek, ale tak naprawdę nie odpowiada na pytanie imo. Liczba spacji nie jest taka sama, jak poziom wcięcia.
wim
1
To nie takie proste. Zastąpienie 4 spacji pojedynczymi spacjami może zmienić logikę kodu.
wim
1
Ale ten kod jest idealny do tego, czego szukał OP: (komentarz OP nr 9): „Chciałem wciąć dane wyjściowe mojego kodu zgodnie z wcięciem w kodzie”. Więc może zrobić coś takiegoprint('{Space}'*get_indentation_level(), x)
Craig Burgler
10

Możesz użyć sys.current_frame.f_lineno, aby uzyskać numer linii. Następnie, aby znaleźć liczbę poziomów wcięć, musisz znaleźć poprzednią linię z zerowym wcięciem, a następnie odejmując bieżący numer linii od numeru tej linii, otrzymasz liczbę wcięć:

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

Próbny:

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

Jeśli chcesz, aby numer poziomu wcięcia był oparty na poprzednich wierszach :, możesz to zrobić z niewielką zmianą:

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

Próbny:

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

Alternatywną odpowiedzią jest tutaj funkcja obliczania liczby wcięć (białych znaków):

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
źródło
pytanie dotyczyło liczby poziomów wcięć, a nie liczby spacji. niekoniecznie są proporcjonalne.
wim
Dla twojego kodu demo wyjście powinno być 1 - 2 - 3 - 3
Craig Burgler
@CraigBurgler Aby uzyskać 1 - 2 - 3 - 3, możemy policzyć liczbę wierszy przed bieżącą linią, które kończą się znakiem a, :dopóki nie napotkamy linii z zerowym wcięciem, sprawdź edycję!
Kasramvd
2
hmmm ... ok ... teraz wypróbuj niektóre przypadki testowe @ user2357112;)
Craig Burgler
@CraigBurgler To rozwiązanie jest tylko w większości przypadków, a jeśli chodzi o tę odpowiedź, jest to również ogólny sposób, nie daje też kompleksowego rozwiązania. Spróbuj{3:4, \n 2:get_ind_num()}
Kasramvd
7

Aby rozwiązać „prawdziwy” problem, który doprowadził do twojego pytania, możesz zaimplementować menedżera kontekstu, który śledzi poziom withwcięć i sprawi, że struktura blokowa w kodzie będzie odpowiadać poziomom wcięć wyjścia. W ten sposób wcięcie kodu nadal odzwierciedla wcięcie wyjścia bez zbytniego łączenia obu. Nadal istnieje możliwość refaktoryzacji kodu na różne funkcje i inne wcięcia oparte na strukturze kodu, które nie zakłócają wcięcia wyjścia.

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

Wynik:

0
  1
    Hallo 2
      3
    and back one level 2
Maczuga
źródło
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
źródło
12
Daje to wcięcie w przestrzeniach, a nie w poziomach. O ile programista nie używa spójnych wartości wcięć, konwersja na poziomy może być brzydka.
Prune
4
Czy funkcja jest nieudokumentowana? Nie mogę go tutaj
GingerPlusPlus