Indentazione corretta per stringhe multilinea Python


456

Qual è il rientro corretto per le stringhe multilinea Python all'interno di una funzione?

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

o

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

o qualcos'altro?

Sembra strano che la stringa penda fuori dalla funzione nel primo esempio.


4
Le dotstring vengono trattate in modo speciale : ogni trattino della prima riga viene rimosso; il rientro comune più piccolo rilevato su tutte le altre linee non vuote viene rimosso da tutte. A parte questo, i letterali a stringhe multilinea in Python sono purtroppo ciò che vedi è ciò che ottieni in termini di spazio bianco: tutti i caratteri tra i delimitatori di stringa diventano parte della stringa, incluso il rientro che, con Python che legge gli istinti, sembra che dovrebbe essere misurato dal rientro della riga in cui inizia il valore letterale.
Evgeni Sergeev,

@EvgeniSergeev Lo strumento di elaborazione esegue questa attività (e ciò dipende in gran parte dalla scelta dello strumento di elaborazione). method.__doc__non viene modificato dallo stesso Python più di qualsiasi altro strletterale.
cz

Risposte:


453

Probabilmente vuoi allinearti con il """

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

Poiché le nuove righe e gli spazi sono inclusi nella stringa stessa, è necessario postelaborarla. Se non vuoi farlo e hai un sacco di testo, potresti voler memorizzarlo separatamente in un file di testo. Se un file di testo non funziona bene per la tua applicazione e non vuoi postprocedere, probabilmente ci andrei

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

Se vuoi postelaborare una stringa multilinea per tagliare le parti che non ti servono, dovresti considerare il textwrapmodulo o la tecnica per postelaborare le stringhe presentate in 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)

10
Questo è lo stile di "sospensione del rientro" della continuazione della linea. È prescritto in PEP8 per scopi come definizioni di funzioni e istruzioni if ​​lunghe, sebbene non menzionate per stringhe multilinea. Personalmente questo è un posto in cui rifiuto di seguire PEP8 (e invece uso il rientro a 4 spazi), poiché non mi piace molto i rientri pendenti, che per me oscurano la struttura corretta del programma.
mercoledì

2
@buffer, in 3.1.2 del tutorial ufficiale ("Due letterali stringa uno accanto all'altro sono automaticamente concatenati ...") e nel riferimento linguistico.
Mike Graham,

5
Il secondo modulo con concatenazione automatica delle stringhe non include newline È una funzionalità.
Mike Graham,

19
La trim()funzione specificata in PEP257 è implementata nella libreria standard come inspect.cleandoc.

2
+1 a @bobince 's commenti su respingendo 'rientri sporgenti' qui ... Soprattutto perché se si cambia il nome della variabile da stringa texto qualcosa di lunghezza diversa, quindi è ora necessario aggiornare il rientro di letteralmente ogni singola riga della stringa multilinea solo per farla corrispondere """correttamente. La strategia di rientro non dovrebbe complicare i futuri refattori / manutenzione, ed è uno dei luoghi in cui la PEP fallisce davvero
kevlarr

255

La textwrap.dedentfunzione consente di iniziare con il rientro corretto nella sorgente , quindi di rimuoverlo dal testo prima dell'uso.

Il compromesso, come notato da alcuni altri, è che questa è una chiamata di funzione extra sul letterale; tenerne conto quando si decide dove posizionare questi valori letterali nel proprio codice.

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)

Il finale \nel messaggio di log letterale è quello di garantire che l'interruzione di riga non sia nel letterale; in questo modo, il letterale non inizia con una riga vuota e inizia invece con la riga completa successiva.

Il valore restituito da textwrap.dedentè la stringa di input con tutte le rientranze di spazi bianchi iniziali comuni rimosse su ogni riga della stringa. Quindi il log_messagevalore sopra sarà:

Prepare to frobnicate:
Here it comes...
    Any moment now.
And: Frobnicate!

2
Sebbene questa sia una soluzione ragionevole e piacevole da sapere, fare qualcosa del genere all'interno di una funzione chiamata frequentemente potrebbe rivelarsi un disastro.
Haridsv,

@haridsv Perché sarebbe un disastro?
jtmoulia,

10
@jtmoulia: Una descrizione migliore del disastro sarebbe "inefficiente" perché il risultato della textwrap.dedent()chiamata è un valore costante, proprio come il suo argomento di input.
martineau,

2
@haridsv l'origine di tale disastro / inefficienza sta definendo una stringa costante all'interno di una funzione chiamata frequentemente. È possibile scambiare la definizione della costante per chiamata con una ricerca per chiamata. In questo modo la preelaborazione della deduzione verrebbe eseguita una sola volta . Una domanda rilevante può essere stackoverflow.com/q/15495376/611007 Elenca le idee per evitare di definire la costante per ogni chiamata. Anche se le alternative sembrano richiedere una ricerca. Tuttavia, vengono tentati vari modi per trovare il posto favorevole per riporlo. Ad esempio: def foo: return foo.xquindi la riga successiva foo.x = textwrap.dedent("bar").
n611x007,

1
Immagino che sarebbe inefficiente se la stringa fosse destinata alla registrazione abilitata solo in modalità debug e non venisse utilizzata diversamente. Ma allora perché registrare letteralmente una stringa multilinea? Quindi è difficile trovare un esempio nella vita reale in cui quanto sopra sarebbe inefficiente (cioè dove rallenta notevolmente il programma), perché qualunque cosa consumi queste stringhe sarà più lenta.
Evgeni Sergeev,

53

Usa inspect.cleandoccosì:

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

Il rientro relativo verrà mantenuto come previsto. Come commentato di seguito, se si desidera mantenere le righe vuote precedenti, utilizzare textwrap.dedent. Tuttavia, ciò mantiene anche la prima interruzione di riga.

Nota: è buona norma rientrare blocchi logici di codice nel relativo contesto per chiarire la struttura. Ad esempio la stringa multilinea appartenente alla variabile string.


5
Così confuso perché questa risposta non esisteva fino ad ora, inspect.cleandocesiste da quando Python 2.6 , che era il 2008 ..? Assolutamente la risposta più pulita, soprattutto perché non utilizza lo stile di rientro sospeso, che spreca solo una quantità inutile di spazio
kevlarr

1
Questa soluzione rimuove le prime righe di testo vuoto (se presenti). Se non vuoi quel comportamento, usa textwrap.dedent docs.python.org/2/library/textwrap.html#textwrap.dedent
joshuakcockrell

1
Questo è perfetto!
zzzz zzzz,

23

Un'opzione che sembra mancare alle altre risposte (menzionata solo in fondo in un commento di naxa) è la seguente:

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

Ciò consentirà un corretto allineamento, unirà le linee in modo implicito e manterrà comunque lo spostamento della linea che, per me, è uno dei motivi per cui vorrei usare comunque le stringhe multilinea.

Non richiede alcun postelaborazione, ma è necessario aggiungere manualmente \nin un determinato punto in cui si desidera terminare la linea. In linea o come stringa separata dopo. Quest'ultimo è più facile da copiare e incollare.


Si noti che questo è un esempio di una stringa implicitamente unita, non di una stringa multilinea.
Trk,

@trk, è multilinea nel senso che la stringa contiene nuove righe (ovvero più righe), ma sì utilizza un join per aggirare i problemi di formattazione dell'OP.
holroy,

17

Alcune altre opzioni. In Ipython con pylab abilitato, dedent è già nello spazio dei nomi. Ho controllato ed è da matplotlib. Oppure può essere importato con:

from matplotlib.cbook import dedent

Nella documentazione afferma che è più veloce di quello equivalente a textwrap e nei miei test su ipython è in effetti 3 volte più veloce in media con i miei test rapidi. Ha anche il vantaggio di scartare eventuali righe vuote iniziali, questo ti consente di essere flessibile nel modo in cui costruisci la stringa:

"""
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'uso del dedotto matplotlib su questi tre esempi darà lo stesso risultato sensato. La funzione di deduzione dell'involucro di testo avrà una riga vuota iniziale con il primo esempio.

Lo svantaggio evidente è che textwrap è nella libreria standard mentre matplotlib è un modulo esterno.

Alcuni compromessi qui ... le funzioni di deduzione rendono il tuo codice più leggibile dove vengono definite le stringhe, ma richiedono l'elaborazione in seguito per ottenere la stringa in formato utilizzabile. Nei docstring è ovvio che è necessario utilizzare il rientro corretto poiché la maggior parte degli usi del docstring eseguirà l'elaborazione richiesta.

Quando ho bisogno di una stringa non lunga nel mio codice trovo il seguente codice certamente brutto in cui lascio cadere la stringa lunga dall'indentazione racchiusa. Sicuramente fallisce su "Bello è meglio che brutto", ma si potrebbe sostenere che è più semplice ed esplicito dell'alternativa dedotta.

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

6

Se desideri una soluzione rapida e semplice e ti salvi dalla digitazione di nuove righe, puoi invece optare per un elenco, ad esempio:

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

Anche se questo non è l'approccio migliore, l'ho usato di volta in volta. Se fai usarlo, è necessario utilizzare una tupla invece di una lista, dal momento che non sta andando essere modificato prima di farsi raggiungere.
Lyndsy Simon,

4

preferisco

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

o

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

1
Questo non risponde alla domanda, perché la domanda afferma esplicitamente che il rientro (all'interno della funzione) è importante.
bignose il

@bignose La domanda diceva "Sembra un po 'strana" che non può essere utilizzata.
lk_vc

come realizzerei questo senza la brutta rientranza?
lfender6445

@lfender6445 bene, forse puoi mettere tutte queste stringhe in un file separato da altri codici ...
lk_vc

3

I miei due centesimi, sfuggono alla fine della riga per ottenere i rientri:

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

1

Sono venuto qui alla ricerca di un semplice 1-liner per rimuovere / correggere il livello di identificazione del docstring per la stampa, senza renderlo disordinato , ad esempio rendendolo "appeso fuori dalla funzione" all'interno dello script.

Ecco cosa ho finito per fare:

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

Ovviamente, se stai rientrando con spazi (ad es. 4) anziché con il tasto Tab, usa invece qualcosa del genere:

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

E non è necessario rimuovere il primo carattere se preferisci che i tuoi argomenti siano così:

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

print string.replace(myfunction.__doc__,'\n\t','\n') 

Questo non riesce su metodi di classe e classi nidificate.
Tacaswell,

1

La prima opzione è quella buona - con rientro incluso. È in stile Python: offre leggibilità per il codice.

Per visualizzarlo correttamente:

print string.lstrip()

Questo sembra il modo più semplice e pulito per formattare stringhe con virgolette triple in modo da non avere gli spazi extra a causa del rientro
Taylor Liss

4
Ciò eliminerà solo gli spazi iniziali nella prima riga di una stringa multilinea. Non aiuta con la formattazione delle seguenti righe.
M. Schlenker,

0

Dipende da come si desidera visualizzare il testo. Se si desidera che tutto sia allineato a sinistra, formattarlo come nel primo frammento o scorrere le linee tagliando a sinistra tutto lo spazio.


5
Il modo in cui gli strumenti di elaborazione docstring lavoro è quello di rimuovere non tutto lo spazio a sinistra, ma tanto come la prima linea frastagliata. Questa strategia è un po 'più sofisticata e ti consente di rientrare e di rispettarla nella stringa postelaborata.
Mike Graham,

0

Per le stringhe puoi subito dopo elaborare la stringa. Per i docstring devi invece elaborare la funzione. Ecco una soluzione per entrambi che è ancora leggibile.

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__

1
L'elaborazione di docstring deve già elaborare un rientro coerente, come descritto in PEP 257 . Esistono già strumenti, ad esempio inspect.cleandoc, che fanno questo nel modo giusto.
bignose il

0

Sto riscontrando un problema simile, il codice è diventato davvero illeggibile usando le multiline, sono uscito con qualcosa del genere

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

sì, all'inizio poteva sembrare terribile, ma la sintassi incorporata era piuttosto complessa e aggiungere qualcosa alla fine (come '\ n "') non era una soluzione


0

È possibile utilizzare questa funzione 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


"""))

Risultato:

"""
line one
    line two
        line three
    line two
line one
"""
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.