Richtige Einrückung für mehrzeilige Python-Zeichenfolgen

456

Was ist der richtige Einzug für mehrzeilige Python-Zeichenfolgen innerhalb einer Funktion?

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

oder

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

oder etwas anderes?

Es sieht irgendwie komisch aus, wenn die Zeichenfolge im ersten Beispiel außerhalb der Funktion hängt.

verstricken
quelle
4
Docstrings werden speziell behandelt : Einrückungen der ersten Zeile werden entfernt; Der kleinste gemeinsame Einzug, der über alle anderen nicht leeren Zeilen übernommen wird, wird aus allen entfernt. Abgesehen davon sind mehrzeilige Zeichenfolgenliterale in Python leider das, was Sie in Bezug auf Leerzeichen sehen: Alle Zeichen zwischen den Zeichenfolgenbegrenzern werden Teil der Zeichenfolge, einschließlich Einrückungen, die mit Python-Leseinstinkten Es sieht so aus, als ob es am Einzug der Zeile gemessen werden sollte, in der das Literal beginnt.
Evgeni Sergeev
@EvgeniSergeev Das Verarbeitungswerkzeug führt diese Aufgabe aus (und das hängt weitgehend von Ihrer Wahl des Verarbeitungswerkzeugs ab). method.__doc__wird von Python selbst nicht mehr als jedes andere strLiteral geändert .
cz

Antworten:

453

Sie möchten sich wahrscheinlich mit dem ausrichten """

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

Da die Zeilenumbrüche und Leerzeichen in der Zeichenfolge selbst enthalten sind, müssen Sie sie nachbearbeiten. Wenn Sie dies nicht möchten und viel Text haben, können Sie ihn separat in einer Textdatei speichern. Wenn eine Textdatei für Ihre Anwendung nicht gut funktioniert und Sie nicht nachbearbeiten möchten, würde ich wahrscheinlich mitmachen

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

Wenn Sie eine mehrzeilige Zeichenfolge nachbearbeiten möchten, um die nicht benötigten Teile zu textwrapentfernen, sollten Sie das in PEP 257 vorgestellte Modul oder die Technik für die Nachbearbeitung von Dokumentzeichenfolgen berücksichtigen :

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
quelle
10
Dies ist der Stil der Zeilenfortsetzung "hängender Einzug". Es ist in PEP8 für Zwecke wie Funktionsdefinitionen und lange if-Anweisungen vorgeschrieben, wird jedoch für mehrzeilige Zeichenfolgen nicht erwähnt. Persönlich ist dies ein Ort, an dem ich mich weigere, PEP8 zu folgen (und stattdessen 4-Leerzeichen-Einrückungen zu verwenden), da ich hängende Einrückungen nicht mag, was für mich die richtige Struktur des Programms verdeckt.
Bobince
2
@buffer, in 3.1.2 des offiziellen Tutorials ("Zwei nebeneinander stehende String-Literale werden automatisch verkettet ...") und in der Sprachreferenz.
Mike Graham
5
Das zweite Formular mit automatischer Zeichenfolgenverkettung enthält keine Zeilenumbrüche. Es ist eine Funktion.
Mike Graham
18
Die trim()in PEP257 angegebene Funktion ist in der Standardbibliothek als implementiert inspect.cleandoc.
2
+1 zu @bobince 's Kommentar über das Ablehnen von "hängenden Einrückungen" hier ... Vor allem, wenn Sie den Variablennamen von stringin textoder etwas anderes ändern , müssen Sie jetzt die Einrückung buchstäblich jeder einzelnen Zeile der Zeile aktualisieren mehrzeiliger String, nur damit er mit dem """richtigen übereinstimmt . Die Einrückungsstrategie sollte zukünftige Refaktoren / Wartungsarbeiten nicht erschweren, und dies ist einer der Orte, an denen PEP wirklich versagt
Kevlarr,
254

Die textwrap.dedentFunktion ermöglicht es, mit der korrekten Einrückung in der Quelle zu beginnen und diese dann vor der Verwendung aus dem Text zu entfernen.

Der Kompromiss besteht, wie einige andere bemerkten, darin, dass dies ein zusätzlicher Funktionsaufruf für das Literal ist; Berücksichtigen Sie dies, wenn Sie entscheiden, wo diese Literale in Ihrem Code platziert werden sollen.

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)

Das Nachlaufen \im Protokollnachrichtenliteral soll sicherstellen, dass der Zeilenumbruch nicht im Literal enthalten ist. Auf diese Weise beginnt das Literal nicht mit einer Leerzeile, sondern mit der nächsten vollständigen Zeile.

Der Rückgabewert von textwrap.dedentist die Eingabezeichenfolge, bei der alle gemeinsamen führenden Leerzeicheneinrückungen in jeder Zeile der Zeichenfolge entfernt werden. Der obige log_messageWert lautet also:

Prepare to frobnicate:
Here it comes...
    Any moment now.
And: Frobnicate!
große Nase
quelle
1
Dies ist zwar eine vernünftige Lösung und gut zu wissen, aber so etwas in einer häufig aufgerufenen Funktion zu tun, könnte sich als Katastrophe erweisen.
Haridsv
@haridsv Warum sollte das eine Katastrophe sein?
Jtmoulia
10
@jtmoulia: Eine bessere Beschreibung als eine Katastrophe wäre "ineffizient", da das Ergebnis des textwrap.dedent()Aufrufs genau wie sein Eingabeargument ein konstanter Wert ist.
Martineau
2
@haridsv Der Ursprung dieser Katastrophe / Ineffizienz ist die Definition einer konstanten Zeichenfolge innerhalb einer häufig aufgerufenen Funktion. Es ist möglich, die Konstantendefinition pro Anruf gegen eine Suche pro Anruf einzutauschen. Auf diese Weise würde die dedent- Vorverarbeitung nur einmal ausgeführt . Eine relevante Frage kann stackoverflow.com/q/15495376/611007 sein. Sie listet Ideen auf, um zu vermeiden, dass die Konstante pro Aufruf definiert wird. Alternativen scheinen jedoch eine Suche zu erfordern. Es werden jedoch verschiedene Möglichkeiten versucht, den günstigen Aufbewahrungsort zu finden. Zum Beispiel: def foo: return foo.xdann nächste Zeile foo.x = textwrap.dedent("bar").
n611x007
1
Ich denke, es wäre ineffizient, wenn die Zeichenfolge für die Protokollierung vorgesehen ist, die nur im Debug-Modus aktiviert ist und ansonsten nicht verwendet wird. Aber warum sollte man dann überhaupt ein mehrzeiliges String-Literal protokollieren? Es ist daher schwierig, ein reales Beispiel zu finden, bei dem das oben Genannte ineffizient wäre (dh das Programm erheblich verlangsamt), da alles, was diese Zeichenfolgen verbraucht, langsamer wird.
Evgeni Sergeev
52

Verwenden Sie inspect.cleandocwie folgt:

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

Die relative Einrückung wird wie erwartet beibehalten. Verwenden Sie, wie unten kommentiert , wenn Sie vorhergehende Leerzeilen beibehalten möchten textwrap.dedent. Damit bleibt aber auch der erste Zeilenumbruch erhalten.

Hinweis: Es wird empfohlen, logische Codeblöcke im zugehörigen Kontext einzurücken, um die Struktur zu verdeutlichen. ZB die mehrzeilige Zeichenfolge, die zur Variablen gehört string.

wihlke
quelle
5
So verwirrt, warum diese Antwort bis jetzt nicht existierte, inspect.cleandocexistiert sie seit Python 2.6 , das war 2008 ..? Absolut die sauberste Antwort, vor allem, weil sie nicht den Stil des hängenden Einzugs verwendet, der nur unnötig viel Platz
verschwendet
1
Diese Lösung entfernt die ersten Zeilen leeren Textes (falls vorhanden). Wenn Sie dieses Verhalten nicht möchten, verwenden Sie textwrap.dedent docs.python.org/2/library/textwrap.html#textwrap.dedent
joshuakcockrell
1
Dies ist perfekt!
zzzz zzzz
23

Eine Option, die in den anderen Antworten zu fehlen scheint (nur tief unten in einem Kommentar von naxa erwähnt), ist die folgende:

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

Dies ermöglicht eine korrekte Ausrichtung, verbindet die Linien implizit und behält dennoch die Linienverschiebung bei, was für mich einer der Gründe ist, warum ich sowieso mehrzeilige Zeichenfolgen verwenden möchte.

Es ist keine Nachbearbeitung erforderlich, aber Sie müssen die \nan einer bestimmten Stelle, an der die Zeile enden soll , manuell hinzufügen . Entweder inline oder als separater String danach. Letzteres lässt sich leichter kopieren und einfügen.

holroy
quelle
Beachten Sie, dass dies ein Beispiel für eine implizit verknüpfte Zeichenfolge ist, keine mehrzeilige Zeichenfolge.
trk
@trk, es ist mehrzeilig in dem Sinne, dass die Zeichenfolge Zeilenumbrüche enthält (auch bekannt als mehrere Zeilen), aber ja, es werden Verknüpfungen verwendet, um die Formatierungsprobleme zu umgehen, die das OP hatte.
Holroy
17

Einige weitere Optionen. In Ipython mit aktiviertem Pylab befindet sich dedent bereits im Namespace. Ich habe nachgesehen und es ist von matplotlib. Oder es kann importiert werden mit:

from matplotlib.cbook import dedent

In der Dokumentation heißt es, dass es schneller ist als das Textwrap-Äquivalent und in meinen Tests in Ipython ist es mit meinen Schnelltests im Durchschnitt dreimal schneller. Es hat auch den Vorteil, dass alle führenden Leerzeilen verworfen werden, sodass Sie beim Erstellen der Zeichenfolge flexibel sein können:

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

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

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

Die Verwendung der Matplotlib für diese drei Beispiele führt zu demselben vernünftigen Ergebnis. Die Textwrap-Dedent-Funktion hat eine führende Leerzeile mit dem ersten Beispiel.

Offensichtlicher Nachteil ist, dass sich Textwrap in der Standardbibliothek befindet, während matplotlib ein externes Modul ist.

Einige Kompromisse hier ... Die dedent-Funktionen machen Ihren Code besser lesbar, wenn die Zeichenfolgen definiert werden, müssen jedoch später verarbeitet werden, um die Zeichenfolge in einem verwendbaren Format zu erhalten. In Docstrings ist es offensichtlich, dass Sie den richtigen Einzug verwenden sollten, da die meisten Verwendungen des Docstrings die erforderliche Verarbeitung ausführen.

Wenn ich eine nicht lange Zeichenfolge in meinem Code benötige, finde ich den folgenden zugegebenermaßen hässlichen Code, in dem ich die lange Zeichenfolge aus dem einschließenden Einzug herausfallen lasse. Scheitert definitiv an "Schön ist besser als hässlich", aber man könnte argumentieren, dass es einfacher und expliziter ist als die dedizierte Alternative.

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
quelle
6

Wenn Sie eine schnelle und einfache Lösung wünschen und sich das Eingeben von Zeilenumbrüchen ersparen möchten, können Sie sich stattdessen für eine Liste entscheiden, z.

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
quelle
Dies ist zwar nicht der beste Ansatz, aber ich habe ihn von Zeit zu Zeit verwendet. Wenn Sie es verwenden, sollten Sie ein Tupel anstelle einer Liste verwenden, da es vor dem Beitritt nicht geändert wird.
Lyndsy Simon
4

ich bevorzuge

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

oder

    def method():
        string = """\
line one
line two
line three\
"""
lk_vc
quelle
1
Dies beantwortet die Frage nicht, da in der Frage ausdrücklich angegeben ist, dass die Einrückung (innerhalb der Funktion) von Bedeutung ist.
Bignose
@bignose Die Frage lautete "Es sieht irgendwie komisch aus" und darf nicht verwendet werden.
lk_vc
Wie würde ich dies ohne die hässliche Einkerbung erreichen?
lfender6445
@ lfender6445 gut, vielleicht können Sie alle diese Zeichenfolgen in eine separate Datei von anderen Codes platzieren ...
lk_vc
3

Meine zwei Cent entkommen dem Zeilenende, um die Einrückungen zu erhalten:

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

Ich bin hierher gekommen, um nach einem einfachen 1-Liner zu suchen, mit dem die Identifikationsstufe des Dokumentstrings zum Drucken entfernt / korrigiert werden kann , ohne dass er unordentlich aussieht , indem er beispielsweise im Skript "außerhalb der Funktion hängt".

Folgendes habe ich getan:

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

Wenn Sie anstelle der Tabulatortaste Leerzeichen (z. B. 4) einrücken, verwenden Sie stattdessen Folgendes:

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

Und Sie müssen das erste Zeichen nicht entfernen, wenn Ihre Dokumentzeichenfolgen stattdessen so aussehen sollen:

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

print string.replace(myfunction.__doc__,'\n\t','\n') 
James Gowdy
quelle
Dies schlägt bei Klassenmethoden und verschachtelten Klassen fehl.
Tacaswell
1

Die erste Option ist die gute - mit Einrückung. Es ist im Python-Stil - bietet Lesbarkeit für den Code.

So zeigen Sie es richtig an:

print string.lstrip()
Bog Dia
quelle
Dies scheint der einfachste und sauberste Weg zu sein, dreifache Anführungszeichen zu formatieren, damit Sie aufgrund von Einrückungen keine zusätzlichen Leerzeichen haben
Taylor Liss
4
Dadurch werden nur führende Leerzeichen in der ersten Zeile einer mehrzeiligen Zeichenfolge gelöscht. Es hilft nicht beim Formatieren der folgenden Zeilen.
M. Schlenker
0

Dies hängt davon ab, wie der Text angezeigt werden soll. Wenn Sie möchten, dass alles linksbündig ausgerichtet ist, formatieren Sie es entweder wie im ersten Snippet oder durchlaufen Sie die Zeilen, die den gesamten Raum nach links kürzen.

Ignacio Vazquez-Abrams
quelle
5
Die Art und Weise, wie Docstring-Verarbeitungswerkzeuge funktionieren, besteht darin, nicht den gesamten Platz auf der linken Seite zu entfernen , sondern so viel wie die erste eingerückte Zeile. Diese Strategie ist etwas ausgefeilter und ermöglicht es Ihnen, sie einzurücken und in der nachbearbeiteten Zeichenfolge zu berücksichtigen.
Mike Graham
0

Für Zeichenfolgen können Sie die Zeichenfolge direkt nach der Verarbeitung verarbeiten. Für Docstrings müssen Sie stattdessen die Funktion nachbearbeiten. Hier ist eine Lösung für beide, die noch lesbar ist.

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
quelle
1
Die Verarbeitung von Dokumentzeichenfolgen muss bereits konsistente Einrückungen verarbeiten, wie in PEP 257 beschrieben . Es gibt bereits Tools - zB inspect.cleandoc- die dies richtig machen.
Bignose
0

Ich habe ein ähnliches Problem, Code wurde mit Multilines wirklich unlesbar, ich kam mit so etwas heraus

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

Ja, am Anfang könnte es schrecklich aussehen, aber die eingebettete Syntax war ziemlich komplex und das Hinzufügen von etwas am Ende (wie '\ n "') war keine Lösung

Frediano Ziglio
quelle
0

Sie können diese Funktion trim_indent verwenden .

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


"""))

Ergebnis:

"""
line one
    line two
        line three
    line two
line one
"""
Luan Silveira
quelle