Una riga di codice Python può conoscere il suo livello di annidamento del rientro?


149

Da qualcosa del genere:

print(get_indentation_level())

    print(get_indentation_level())

        print(get_indentation_level())

Vorrei ottenere qualcosa del genere:

1
2
3

Il codice può leggere se stesso in questo modo?

Tutto quello che voglio è che l'output delle parti più nidificate del codice sia più nidificato. Allo stesso modo in cui ciò semplifica la lettura del codice, renderebbe più semplice la lettura dell'output.

Ovviamente potrei implementarlo manualmente, usando ad esempio .format(), ma quello che avevo in mente era una funzione di stampa personalizzata che sarebbe print(i*' ' + string)dove si itrova il livello di rientro. Questo sarebbe un modo rapido per rendere l'output leggibile sul mio terminale.

Esiste un modo migliore per farlo che eviti una scrupolosa formattazione manuale?


71
Sono davvero curioso di sapere perché ne hai bisogno.
Harrison,

13
@Harrison Volevo rientrare l'output del mio codice in base a come è stato rientrato nel codice.
Fab von Bellingshausen,

14
La vera domanda è: perché dovresti averne bisogno? Il livello di rientro è statico; lo sai con certezza quando inserisci la get_indentation_level()dichiarazione nel tuo codice. Puoi fare altrettanto print(3)o qualunque cosa direttamente. Ciò che potrebbe essere più iterativo è l'attuale livello di annidamento nello stack di chiamate di funzione.
tobias_k,

19
Ha lo scopo di eseguire il debug del codice? Questo sembra un modo super geniale per registrare il flusso di esecuzione o come una soluzione superingegnerizzata per un semplice problema, e non sono sicuro di quale sia ... forse entrambi!
Blackhawk,

7
@FabvonBellingshausen: Sembra che sarebbe molto meno leggibile di quanto speri. Penso che potresti essere meglio servito passando esplicitamente attorno a un depthparametro e aggiungendo il valore appropriato ad esso se necessario quando lo passi ad altre funzioni. È probabile che l'annidamento del codice non corrisponda in modo chiaro al rientro desiderato dall'output.
user2357112 supporta Monica il

Risposte:


115

Se si desidera il rientro in termini di livello di annidamento anziché spazi e tabulazioni, le cose si complicano. Ad esempio, nel seguente codice:

if True:
    print(
get_nesting_level())

la chiamata a get_nesting_levelè effettivamente annidata di un livello in profondità, nonostante il fatto che non vi siano spazi bianchi iniziali sulla linea della get_nesting_levelchiamata. Nel frattempo, nel seguente codice:

print(1,
      2,
      get_nesting_level())

la chiamata a get_nesting_levelè annidata a zero livelli di profondità, nonostante la presenza di spazi bianchi iniziali sulla sua linea.

Nel seguente codice:

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

if True:
    print(get_nesting_level())

le due chiamate get_nesting_levelsono a livelli di nidificazione diversi, nonostante il fatto che lo spazio bianco iniziale sia identico.

Nel seguente codice:

if True: print(get_nesting_level())

sono zero livelli nidificati o uno? In termini di INDENTe DEDENTtoken nella grammatica formale, ha zero livelli di profondità, ma potresti non sentirti allo stesso modo.


Se vuoi farlo, dovrai tokenizzare l'intero file fino al punto della chiamata, contare INDENTe DEDENTtoken. Il tokenizemodulo sarebbe molto utile per tale funzione:

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
Questo non funziona come mi aspetterei quando get_nesting_level()viene chiamato all'interno di quella chiamata di funzione returns restituisce il livello di annidamento all'interno di quella funzione. Potrebbe essere riscritto per restituire il livello di nidificazione "globale"?
Fab von Bellingshausen,

11
@FabvonBellingshausen: è possibile che il livello di annidamento del rientro e il livello di annidamento delle chiamate di funzioni siano confusi. Questa funzione fornisce il livello di annidamento del rientro. Il livello di annidamento delle chiamate di funzione sarebbe piuttosto diverso e darebbe un livello di 0 per tutti i miei esempi. Se si desidera una sorta di ibrido di rientro / annidamento delle chiamate che incrementa sia per le chiamate di funzione sia per le strutture di flusso di controllo come whilee with, sarebbe fattibile, ma non è quello che hai chiesto, e cambiando la domanda per fare qualcosa di diverso a questo punto sarebbe una cattiva idea.
user2357112 supporta Monica il

38
Per inciso, sono in completo accordo con tutte le persone che dicono che questa è una cosa davvero strana da fare. C'è probabilmente un modo molto migliore per risolvere qualsiasi problema tu stia cercando di risolvere, e fare affidamento su questo è probabile che ti tenderà al tendine del ginocchio costringendoti a usare tutti i tipi di hack cattivi per evitare di cambiare il rientro o la struttura delle chiamate di funzione quando devi fare modifiche al tuo codice.
user2357112 supporta Monica il

4
di certo non mi aspettavo che qualcuno avesse effettivamente risposto a questa domanda. (considera il linecachemodulo per cose come questa - è usato per stampare traceback e può gestire moduli importati da file zip e altri strani trucchi di importazione)
Eevee

6
@Eevee: certamente non mi aspettavo che così tante persone lo votassero ! linecachepotrebbe essere utile per ridurre la quantità di I / O dei file (e grazie per avermelo ricordato), ma se avessi iniziato a ottimizzarlo, sarei infastidito dal modo in cui ridistribuiamo ridondantemente lo stesso file per le ripetizioni del stessa chiamata o per più siti di chiamata all'interno dello stesso file. Ci sono molti modi in cui possiamo ottimizzarlo, ma non sono sicuro di quanto voglia davvero mettere a punto e rendere a prova di proiettile questa cosa folle.
user2357112 supporta Monica il

22

Sì, è sicuramente possibile, ecco un esempio funzionante:

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
In relazione al commento fatto da @Prune, questo può essere fatto per restituire il rientro in livelli anziché in spazi? Sarà sempre ok dividere semplicemente per 4?
Fab von Bellingshausen,

2
No, dividere per 4 per ottenere il rientro non funzionerà con questo codice. Può verificare aumentando il livello di rientro dell'ultima istruzione di stampa, l'ultimo valore stampato viene appena aumentato.
Craig Burgler,

10
Un buon inizio, ma in realtà non risponde alla domanda imo. Il numero di spazi non è uguale al livello di rientro.
mercoledì

1
Non è così semplice. La sostituzione di 4 spazi con spazi singoli può modificare la logica del codice.
mercoledì

1
Ma questo codice è perfetto per ciò che l'OP stava cercando: (commento OP n. 9): "Volevo rientrare l'output del mio codice in base a come era rientrato nel codice". Quindi può fare qualcosa del genereprint('{Space}'*get_indentation_level(), x)
Craig Burgler il

10

È possibile utilizzare sys.current_frame.f_linenoper ottenere il numero di riga. Quindi, per trovare il numero del livello di rientro devi trovare la riga precedente con rientro zero quindi sottraendo il numero di riga corrente dal numero di quella riga otterrai il numero di rientro:

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

Se vuoi il numero del livello di rientro in base alle righe precedenti, :puoi semplicemente farlo con un piccolo cambiamento:

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

E come risposta alternativa qui è una funzione per ottenere il numero di rientri (spazi bianchi):

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

domanda per numero di livelli di rientro, non numero di spazi. non sono necessariamente proporzionali.
mercoledì

Per il tuo codice demo l'output dovrebbe essere 1 - 2 - 3 - 3
Craig Burgler il

@CraigBurgler Per ottenere 1 - 2 - 3 - 3 possiamo contare il numero di righe prima della riga corrente che termina con un :fino a quando non incontriamo la riga con rientro zero, Dai un'occhiata alla modifica!
Kasramvd,

2
hmmm ... ok ... ora prova alcuni dei casi di test di @ user2357112;)
Craig Burgler,

@CraigBurgler Questa soluzione è solo per la maggior parte dei casi, e per quanto riguarda quella risposta, è anche un modo generale, ma non offre una soluzione completa. Prova{3:4, \n 2:get_ind_num()}
Kasramvd,

7

Per risolvere il problema "reale" che porta alla tua domanda, potresti implementare un gestore di contesto che tenga traccia del livello di withrientro e faccia corrispondere la struttura a blocchi nel codice ai livelli di rientro dell'output. In questo modo il rientro del codice riflette ancora il rientro in uscita senza accoppiare entrambi troppo. È ancora possibile riformattare il codice in diverse funzioni e avere altri rientri basati sulla struttura del codice che non interferisce con il rientro di output.

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

Produzione:

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
Questo dà il rientro negli spazi, non nei livelli. A meno che il programmatore non utilizzi importi di rientro coerenti, questo potrebbe essere brutto da convertire in livelli.
Pota il

4
La funzione non è documentata? Non riesco a trovarlo qui
GingerPlusPlus
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.