Indentation appropriée pour les chaînes multilignes Python

456

Quelle est l'indentation appropriée pour les chaînes multilignes Python dans une fonction?

    def method():
        string = """line one
line two
line three"""

ou

    def method():
        string = """line one
        line two
        line three"""

ou autre chose?

Il semble assez étrange d'avoir la chaîne suspendue en dehors de la fonction dans le premier exemple.

piéger
la source
4
Les docstrings sont traitées spécialement : tout retrait de la première ligne est supprimé; le plus petit retrait commun repris sur toutes les autres lignes non vides est supprimé de tous. En dehors de cela, les littéraux de chaînes multilignes en Python sont malheureusement ce que vous voyez est ce que vous obtenez en termes d'espaces: tous les caractères entre les délimiteurs de chaînes font partie de la chaîne, y compris l'indentation qui, avec les instincts de lecture Python, semble qu'il devrait être mesuré à partir du retrait de la ligne où le littéral commence.
Evgeni Sergeev
@EvgeniSergeev L'outil de traitement effectue cette tâche (et cela dépend en grande partie de votre choix d'outil de traitement). method.__doc__n'est pas modifié par Python lui-même plus que tout autre strlittéral.
cz

Réponses:

453

Vous voulez probablement vous aligner avec le """

def foo():
    string = """line one
             line two
             line three"""

Étant donné que les retours à la ligne et les espaces sont inclus dans la chaîne elle-même, vous devrez la post-traiter. Si vous ne voulez pas faire cela et que vous avez beaucoup de texte, vous pouvez le stocker séparément dans un fichier texte. Si un fichier texte ne fonctionne pas bien pour votre application et que vous ne voulez pas post-traiter, j'irais probablement avec

def foo():
    string = ("this is an "
              "implicitly joined "
              "string")

Si vous souhaitez post-traiter une chaîne multiligne pour supprimer les parties dont vous n'avez pas besoin, vous devez considérer le textwrapmodule ou la technique de post-traitement des docstrings présentés dans PEP 257 :

def trim(docstring):
    if not docstring:
        return ''
    # Convert tabs to spaces (following the normal Python rules)
    # and split into a list of lines:
    lines = docstring.expandtabs().splitlines()
    # Determine minimum indentation (first line doesn't count):
    indent = sys.maxint
    for line in lines[1:]:
        stripped = line.lstrip()
        if stripped:
            indent = min(indent, len(line) - len(stripped))
    # Remove indentation (first line is special):
    trimmed = [lines[0].strip()]
    if indent < sys.maxint:
        for line in lines[1:]:
            trimmed.append(line[indent:].rstrip())
    # Strip off trailing and leading blank lines:
    while trimmed and not trimmed[-1]:
        trimmed.pop()
    while trimmed and not trimmed[0]:
        trimmed.pop(0)
    # Return a single string:
    return '\n'.join(trimmed)
Mike Graham
la source
10
Il s'agit du style de «continuation à tirets suspendus». Il est prescrit dans PEP8 à des fins telles que les définitions de fonction et les instructions if longues, bien qu'il ne soit pas mentionné pour les chaînes multilignes. Personnellement, c'est un endroit où je refuse de suivre PEP8 (et d'utiliser la mise en retrait à 4 espaces à la place), car je n'aime pas du tout les retraits suspendus, ce qui pour moi obscurcit la bonne structure du programme.
bobince
2
@buffer, dans 3.1.2 du tutoriel officiel ("Deux littéraux de chaînes côte à côte sont automatiquement concaténés ...") et dans la référence du langage.
Mike Graham
5
Le deuxième formulaire avec concaténation automatique des chaînes n'inclut pas la nouvelle ligne. C'est une fonctionnalité.
Mike Graham
19
La trim()fonction spécifiée dans PEP257 est implémentée dans la bibliothèque standard en tant que inspect.cleandoc.
2
+1 à @bobince « de commentaire au sujet de rejeter « suspendus » ici indentations ... Surtout parce que si vous modifiez le nom de la variable de stringla textou quoi que ce soit d'une longueur différente, vous devez maintenant mettre à jour l'empreinte de littéralement chaque ligne de la chaîne multiligne juste pour le faire correspondre avec le """correctement. La stratégie d'indentation ne devrait pas compliquer les futurs refactors / maintenance, et c'est l'un des endroits où PEP échoue vraiment
kevlarr
255

La textwrap.dedentfonction permet de commencer avec une indentation correcte dans la source , puis de la retirer du texte avant de l'utiliser.

Le compromis, comme l'ont noté certains autres, est qu'il s'agit d'un appel de fonction supplémentaire sur le littéral; Tenez-en compte lorsque vous décidez où placer ces littéraux dans votre code.

import textwrap

def frobnicate(param):
    """ Frobnicate the scrognate param.

        The Weebly-Ruckford algorithm is employed to frobnicate
        the scrognate to within an inch of its life.

        """
    prepare_the_comfy_chair(param)
    log_message = textwrap.dedent("""\
            Prepare to frobnicate:
            Here it comes...
                Any moment now.
            And: Frobnicate!""")
    weebly(param, log_message)
    ruckford(param)

La fin \du littéral du message de journal est de s'assurer que le saut de ligne n'est pas dans le littéral; De cette façon, le littéral ne commence pas par une ligne vierge et commence à la place par la ligne complète suivante.

La valeur de retour de textwrap.dedentest la chaîne d'entrée avec toutes les indentations d'espaces en tête courantes supprimées sur chaque ligne de la chaîne. La log_messagevaleur ci-dessus sera donc:

Prepare to frobnicate:
Here it comes...
    Any moment now.
And: Frobnicate!
gros nez
la source
2
Bien que ce soit une solution raisonnable et agréable à savoir, faire quelque chose comme ça dans une fonction fréquemment appelée pourrait s'avérer être un désastre.
haridsv
@haridsv Pourquoi serait-ce un désastre?
jtmoulia
10
@jtmoulia: Une meilleure description qu'une catastrophe serait "inefficace" car le résultat de l' textwrap.dedent()appel est une valeur constante, tout comme son argument d'entrée.
martineau
2
@haridsv l'origine de cette catastrophe / inefficacité est de définir une chaîne constante à l' intérieur d' une fonction fréquemment appelée. Possibilité d'échanger la définition de constante par appel pour une recherche par appel. De cette façon, le prétraitement dédent ne s'exécuterait qu'une seule fois . Une question pertinente peut être stackoverflow.com/q/15495376/611007 Il énumère des idées pour éviter de définir la constante pour chaque appel. Bien que les alternatives semblent nécessiter une recherche. Pourtant, diverses façons de trouver l'endroit favorable pour le stocker sont tentées. Par exemple: def foo: return foo.xpuis la ligne suivante foo.x = textwrap.dedent("bar").
n611x007
1
Je suppose que ce serait inefficace si la chaîne est destinée à la journalisation qui n'est activée qu'en mode débogage, et ne sera pas utilisée autrement. Mais alors pourquoi enregistrer un littéral de chaîne multiligne de toute façon? Il est donc difficile de trouver un exemple réel où ce qui précède serait inefficace (c'est-à-dire où cela ralentit considérablement le programme), car tout ce qui consomme ces chaînes va être plus lent.
Evgeni Sergeev
53

Utilisez inspect.cleandoccomme ça:

def method():
    string = inspect.cleandoc("""
        line one
        line two
        line three""")

L'indentation relative sera maintenue comme prévu. Comme commenté ci - dessous, si vous voulez garder précédent lignes vides, utilisation textwrap.dedent. Cependant, cela conserve également le premier saut de ligne.

Remarque: Il est recommandé d'indenter des blocs logiques de code dans son contexte associé pour clarifier la structure. Par exemple, la chaîne multi-lignes appartenant à la variable string.

wihlke
la source
5
Tellement confus pourquoi cette réponse n'existait pas jusqu'à présent, inspect.cleandocexiste depuis Python 2.6 , qui était 2008 ..? Absolument la réponse la plus propre, surtout parce qu'elle n'utilise pas le style de retrait suspendu, qui gaspille juste une quantité d'espace inutile
kevlarr
1
Cette solution supprime les premières lignes de texte vide (le cas échéant). Si vous ne souhaitez pas ce comportement, utilisez textwrap.dedent docs.python.org/2/library/textwrap.html#textwrap.dedent
joshuakcockrell
1
C'est parfait!
zzzz zzzz
23

Une option qui semble manquer dans les autres réponses (uniquement mentionnée au fond dans un commentaire de naxa) est la suivante:

def foo():
    string = ("line one\n"          # Add \n in the string
              "line two"  "\n"      # Add "\n" after the string
              "line three\n")

Cela permettra un alignement correct, joindra les lignes implicitement et gardera toujours le décalage de ligne qui, pour moi, est l'une des raisons pour lesquelles je voudrais quand même utiliser des chaînes multilignes.

Il ne nécessite aucun post-traitement, mais vous devez ajouter manuellement le \nà n'importe quel endroit où vous souhaitez que la ligne se termine. Soit en ligne, soit en tant que chaîne distincte après. Ce dernier est plus facile à copier-coller.

holroy
la source
Notez qu'il s'agit d'un exemple de chaîne implicitement jointe, et non d'une chaîne multiligne.
trk
@trk, il est multiligne dans le sens où la chaîne contient des sauts de ligne (alias plusieurs lignes), mais oui, il utilise la jonction pour contourner les problèmes de formatage que l'OP avait.
holroy
17

Quelques options supplémentaires. Dans Ipython avec pylab activé, dedent est déjà dans l'espace de noms. J'ai vérifié et c'est de matplotlib. Ou il peut être importé avec:

from matplotlib.cbook import dedent

Dans la documentation, il indique qu'il est plus rapide que l'équivalent textwrap et dans mes tests en ipython, il est en effet 3 fois plus rapide en moyenne avec mes tests rapides. Il a également l'avantage de supprimer toutes les lignes vides principales, ce qui vous permet d'être flexible dans la façon dont vous construisez la chaîne:

"""
line 1 of string
line 2 of string
"""

"""\
line 1 of string
line 2 of string
"""

"""line 1 of string
line 2 of string
"""

L'utilisation du matplotlib en fonction de ces trois exemples donnera le même résultat raisonnable. La fonction de dédoublement du texte aura une première ligne vierge avec le 1er exemple.

L'inconvénient évident est que textwrap est dans la bibliothèque standard tandis que matplotlib est un module externe.

Quelques compromis ici ... les fonctions dédentes rendent votre code plus lisible là où les chaînes sont définies, mais nécessitent un traitement ultérieur pour obtenir la chaîne au format utilisable. Dans docstrings, il est évident que vous devez utiliser une indentation correcte car la plupart des utilisations de docstring feront le traitement requis.

Lorsque j'ai besoin d'une chaîne non longue dans mon code, je trouve le code suivant, certes laid, où je laisse la longue chaîne tomber de l'indentation englobante. Échoue définitivement sur "Beau, c'est mieux que laid.", Mais on pourrait dire qu'il est plus simple et plus explicite que l'alternative dédente.

def example():
    long_string = '''\
Lorem ipsum dolor sit amet, consectetur adipisicing
elit, sed do eiusmod tempor incididunt ut labore et
dolore magna aliqua. Ut enim ad minim veniam, quis
nostrud exercitation ullamco laboris nisi ut aliquip.\
'''
    return long_string

print example()
Joop
la source
6

Si vous voulez une solution rapide et facile et évitez de taper des retours à la ligne, vous pouvez opter pour une liste à la place, par exemple:

def func(*args, **kwargs):
    string = '\n'.join([
        'first line of very long string and',
        'second line of the same long thing and',
        'third line of ...',
        'and so on...',
        ])
    print(string)
    return
steabert
la source
Bien que ce ne soit pas la meilleure approche, je l'ai utilisée de temps en temps. Si vous ne l' utiliser, vous devez utiliser un tuple au lieu d'une liste, car il ne va pas être modifié avant d' être rejoint.
Lyndsy Simon
4

je préfère

    def method():
        string = \
"""\
line one
line two
line three\
"""

ou

    def method():
        string = """\
line one
line two
line three\
"""
lk_vc
la source
1
Cela ne répond pas à la question, car la question indique explicitement que l'indentation (au sein de la fonction) est importante.
bignose
@bignose La question disait "Ça a l'air un peu bizarre" non autorisé à utiliser.
lk_vc
comment pourrais-je accomplir cela sans l'indentation laide?
lfender6445
@ lfender6445 eh bien, vous pouvez peut-être placer toutes ces chaînes dans un fichier distinct des autres codes ...
lk_vc
3

Mes deux cents, échappez à la fin de la ligne pour obtenir les retraits:

def foo():
    return "{}\n"\
           "freq: {}\n"\
           "temp: {}\n".format( time, freq, temp )
Simon
la source
1

Je suis venu ici à la recherche d'une simple doublure pour supprimer / corriger le niveau d'identification de la docstring pour l'impression, sans le rendre désordonné , par exemple en le faisant "accrocher en dehors de la fonction" dans le script.

Voici ce que j'ai fini par faire:

import string
def myfunction():

    """
    line 1 of docstring
    line 2 of docstring
    line 3 of docstring"""

print str(string.replace(myfunction.__doc__,'\n\t','\n'))[1:] 

Évidemment, si vous indentez avec des espaces (par exemple 4) plutôt que la touche de tabulation, utilisez quelque chose comme ceci à la place:

print str(string.replace(myfunction.__doc__,'\n    ','\n'))[1:]

Et vous n'avez pas besoin de supprimer le premier caractère si vous souhaitez que vos docstrings ressemblent à ceci:

    """line 1 of docstring
    line 2 of docstring
    line 3 of docstring"""

print string.replace(myfunction.__doc__,'\n\t','\n') 
James Gowdy
la source
Cela échoue sur les méthodes de classe et les classes imbriquées.
tacaswell
1

La première option est la bonne - avec une indentation incluse. Il est en style python - offre une lisibilité pour le code.

Pour l'afficher correctement:

print string.lstrip()
Bog Dia
la source
Cela semble être le moyen le plus simple et le plus propre de formater les chaînes de guillemets triples afin que vous n'ayez pas les espaces supplémentaires en raison de l'indentation
Taylor Liss
4
Cela supprimera uniquement les espaces de début de la première ligne d'une chaîne multiligne. Cela n'aide pas à mettre en forme les lignes suivantes.
M. Schlenker
0

Cela dépend de la façon dont vous souhaitez que le texte s'affiche. Si vous souhaitez que tout soit aligné à gauche, formatez-le comme dans le premier extrait ou parcourez les lignes en coupant à gauche tout l'espace.

Ignacio Vazquez-Abrams
la source
5
Le fonctionnement des outils de traitement de docstring consiste à supprimer non pas tout l'espace à gauche, mais autant que la première ligne en retrait. Cette stratégie est un peu plus sophistiquée et vous permet de mettre en retrait et de la faire respecter dans la chaîne post-traitée.
Mike Graham
0

Pour les chaînes, vous pouvez juste après le traitement de la chaîne. Pour les docstrings, vous devez à la place traiter la fonction. Voici une solution pour les deux qui est toujours lisible.

class Lstrip(object):
    def __rsub__(self, other):
        import re
        return re.sub('^\n', '', re.sub('\n$', '', re.sub('\n\s+', '\n', other)))

msg = '''
      Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
      tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
      veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
      commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
      velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat
      cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
      est laborum.
      ''' - Lstrip()

print msg

def lstrip_docstring(func):
    func.__doc__ = func.__doc__ - Lstrip()
    return func

@lstrip_docstring
def foo():
    '''
    Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
    tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
    veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
    commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
    velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat
    cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
    est laborum.
    '''
    pass


print foo.__doc__
geckos
la source
1
Le traitement des docstrings doit déjà traiter une indentation cohérente, comme décrit dans PEP 257 . Il existe déjà des outils - par exemple inspect.cleandoc- qui le font de la bonne façon.
bignose
0

J'ai un problème similaire, le code est devenu vraiment illisible en utilisant les multilignes, je suis sorti avec quelque chose comme

print("""aaaa
"""   """bbb
""")

oui, au début, cela pouvait sembler terrible, mais la syntaxe intégrée était assez complexe et ajouter quelque chose à la fin (comme '\ n "') n'était pas une solution

Frediano Ziglio
la source
0

Vous pouvez utiliser cette fonction trim_indent .

import re


def trim_indent(s: str):
    s = re.sub(r'^\n+', '', s)
    s = re.sub(r'\n+$', '', s)
    spaces = re.findall(r'^ +', s, flags=re.MULTILINE)
    if len(spaces) > 0 and len(re.findall(r'^[^\s]', s, flags=re.MULTILINE)) == 0:
        s = re.sub(r'^%s' % (min(spaces)), '', s, flags=re.MULTILINE)
    return s


print(trim_indent("""


        line one
            line two
                line three
            line two
        line one


"""))

Résultat:

"""
line one
    line two
        line three
    line two
line one
"""
Luan Silveira
la source