web-dev-qa-db-fra.com

Une ligne de code Python) peut-elle connaître son niveau d'imbrication d'indentation?

De quelque chose comme ça:

print(get_indentation_level())

    print(get_indentation_level())

        print(get_indentation_level())

J'aimerais avoir quelque chose comme ça:

1
2
3

Le code peut-il se lire de cette manière?

Tout ce que je veux, c'est que la sortie des parties les plus imbriquées du code soit plus imbriquée. De la même manière que cela rend le code plus facile à lire, cela rendrait la sortie plus facile à lire.

Bien sûr, je pourrais implémenter cela manuellement, en utilisant par exemple .format(), mais je pensais à une fonction d'impression personnalisée qui print(i*' ' + string)i correspond au niveau d'indentation. Ce serait un moyen rapide de rendre la sortie lisible sur mon terminal.

Y a-t-il une meilleure façon de le faire qui évite un formatage manuel fastidieux?

147

Si vous voulez une indentation en termes de niveau d'imbrication plutôt que d'espaces et de tabulations, les choses deviennent difficiles. Par exemple, dans le code suivant:

if True:
    print(
get_nesting_level())

l'appel à get_nesting_level est en fait imbriqué au niveau le plus profond, malgré le fait qu'il n'y ait pas d'espace blanc au début de la ligne de l'appel get_nesting_level. En attendant, dans le code suivant:

print(1,
      2,
      get_nesting_level())

l'appel de get_nesting_level est imbriqué aux niveaux zéro, malgré la présence de grands espaces sur sa ligne.

Dans le code suivant:

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

if True:
    print(get_nesting_level())

les deux appels à get_nesting_level se trouvent à des niveaux d'imbrication différents, malgré le fait que les espaces blancs de tête soient identiques.

Dans le code suivant:

if True: print(get_nesting_level())

est-ce des niveaux zéro imbriqués, ou un? En termes de jetons INDENT et DEDENT dans la grammaire formelle, c'est zéro niveau de profondeur, mais vous pourriez ne pas ressentir la même chose.


Si vous voulez faire cela, vous devrez tokenize le fichier entier jusqu'au point de l'appel et compter les jetons INDENT et DEDENT. Le module tokenize serait très utile pour une telle fonction:

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

Oui, c'est tout à fait possible, voici un exemple de travail:

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

Vous pouvez utiliser sys.current_frame.f_lineno afin d’obtenir le numéro de ligne. Ensuite, afin de trouver le niveau d'indentation, vous devez rechercher la ligne précédente avec une indentation nulle, puis soustraire le numéro de ligne actuel du numéro de cette ligne, vous obtiendrez le numéro d'indentation:

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

Démo:

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

Si vous voulez le numéro du niveau d'indentation basé sur les lignes précédentes avec : vous pouvez le faire avec un peu de changement:

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

Démo:

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

Et comme autre solution, voici une fonction permettant d’obtenir le nombre d’indentations (espaces):

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

Pour résoudre le problème "réel" qui a conduit à votre question, vous pouvez implémenter un gestionnaire de contexte qui garde une trace du niveau d'indentation et fait correspondre la structure de bloc with du code aux niveaux d'indentation de la sortie. De cette façon, l'indentation du code reflète toujours l'indentation de sortie sans trop coupler les deux. Il est encore possible de refactoriser le code dans différentes fonctions et d’avoir d’autres indentations basées sur la structure du code sans perturber l’indentation de sortie.

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

Sortie:

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