Prawidłowe wcięcie dla wielowierszowych ciągów Python

456

Jakie jest właściwe wcięcie multilinii w Pythonie w obrębie funkcji?

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

lub

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

albo coś innego?

To dziwne, że ciąg wisi na zewnątrz funkcji w pierwszym przykładzie.

usidlić
źródło
4
Ciągi znaków traktowane są specjalnie : każde wcięcie pierwszego wiersza jest usuwane; najmniejsze wspólne wcięcie przejęte przez wszystkie inne niepuste linie jest usuwane z nich wszystkich. Poza tym, wieloliniowe literały łańcuchowe w Pythonie są niestety tym, co widzisz, jeśli chodzi o białe znaki: wszystkie znaki między ogranicznikami łańcucha stają się częścią łańcucha, w tym wcięcie, które przy instynkcie czytania w Pythonie, wygląda na to, że powinien być mierzony od wcięcia linii, w której zaczyna się literał.
Evgeni Sergeev
@EvgeniSergeev Narzędzie przetwarzania wykonuje to zadanie (i to w dużej mierze zależy od wybranego narzędzia przetwarzania). method.__doc__nie jest modyfikowany przez sam Python bardziej niż jakikolwiek inny strliterał.
cz

Odpowiedzi:

453

Prawdopodobnie chcesz się połączyć z """

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

Ponieważ znaki nowego wiersza i spacje są zawarte w samym ciągu, należy go przetworzyć później. Jeśli nie chcesz tego robić i masz dużo tekstu, możesz przechowywać go osobno w pliku tekstowym. Jeśli plik tekstowy nie działa dobrze w twojej aplikacji i nie chcesz postprocesować, prawdopodobnie bym poszedł

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

Jeśli chcesz przetworzyć łańcuch wielowierszowy w celu wycięcia niepotrzebnych części, powinieneś rozważyć textwrapmoduł lub technikę przetwarzania dokumentów w postprocessingu przedstawioną w 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
źródło
10
Jest to styl kontynuacji linii „wiszące wcięcie”. Jest on przewidziany w PEP8 do celów takich jak definicje funkcji i długie instrukcje if, choć nie wspomniano o ciągach wielowierszowych. Osobiście jest to jedno miejsce, w którym odmawiam podążania za PEP8 (i zamiast tego używam wcięcia 4-spacji), ponieważ zdecydowanie nie lubię wieszania wcięć, które dla mnie zaciemniają właściwą strukturę programu.
Bob
2
@buffer, w 3.1.2 oficjalnego samouczka („Dwa literały łańcuchowe obok siebie są automatycznie łączone ...”) oraz w odnośniku językowym.
Mike Graham
5
Druga forma z automatycznym łączeniem łańcuchów nie zawiera znaku nowej linii. Jest to funkcja.
Mike Graham
18
trim()Funkcja, jak określono w PEP257 jest zaimplementowana w bibliotece standardowej jak inspect.cleandoc.
2
+1 do komentarza @bobince na temat odrzucania „wiszących wcięć” tutaj ... Zwłaszcza dlatego, że jeśli zmienisz nazwę zmiennej z stringna textlub cokolwiek o innej długości, musisz teraz zaktualizować wcięcie dosłownie każdej linii łańcuch wielowierszowy , aby dopasować go do """właściwego. Strategia wcięć nie powinna komplikować przyszłych refaktorów / konserwacji i jest to jedno z miejsc, w których PEP naprawdę zawodzi
kevlarr
253

textwrap.dedentFunkcja pozwala zacząć od prawidłowego wcięcia w źródle , a następnie rozebrać go z tekstu przed użyciem.

Kompromis, jak zauważają niektórzy inni, polega na tym, że jest to dodatkowe wywołanie funkcji dosłowne; weź to pod uwagę przy podejmowaniu decyzji, gdzie umieścić te literały w kodzie.

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)

Końcowym \dosłownym komunikatem dziennika jest upewnienie się, że podział wiersza nie jest w tym dosłownym; w ten sposób dosłowność nie zaczyna się pustą linią, a zaczyna od następnej pełnej linii.

Zwracana wartość z textwrap.dedentjest łańcuchem wejściowym ze wszystkimi typowymi wiodącymi wcięciami białych znaków usuniętymi w każdym wierszu łańcucha. Tak więc powyższa log_messagewartość będzie:

Prepare to frobnicate:
Here it comes...
    Any moment now.
And: Frobnicate!
duży nos
źródło
1
Chociaż jest to rozsądne rozwiązanie i miło wiedzieć, robienie czegoś takiego w często wywoływanej funkcji może okazać się katastrofą.
haridsv
@haridsv Dlaczego miałoby to być katastrofą?
jtmoulia
10
@ jtmoulia: Lepszy opis niż katastrofa byłby „nieefektywny”, ponieważ wynikiem textwrap.dedent()wywołania jest stała wartość, podobnie jak argument wejściowy.
martineau,
2
@haridsv źródłem katastrofy / nieefektywności jest zdefiniowanie stałego ciągu wewnątrz często wywoływanej funkcji. Można wymienić stałą definicję dla połączenia dla wyszukiwania dla połączenia. W ten sposób dedykowane przetwarzanie wstępne będzie działać tylko raz . Istotnym pytaniem może być stackoverflow.com/q/15495376/611007 Wymienia pomysły, które pozwolą uniknąć definiowania stałej dla każdego połączenia. Chociaż alternatywy wydają się wymagać sprawdzenia. Mimo to podejmowane są próby znalezienia dogodnego miejsca do przechowywania. Na przykład: def foo: return foo.xnastępnie następny wiersz foo.x = textwrap.dedent("bar").
n611x007,
1
Sądzę, że byłoby nieefektywne, jeśli ciąg przeznaczony jest do rejestrowania, który jest włączony tylko w trybie debugowania, a w przeciwnym razie nie byłby używany. Ale dlaczego w ogóle loguje się literał ciągu wielowierszowego? Trudno więc znaleźć prawdziwy przykład, w którym powyższe byłoby nieefektywne (tj. Gdzie znacznie spowalnia program), ponieważ cokolwiek zużywa te łańcuchy, będzie wolniejsze.
Evgeni Sergeev
52

Użyj inspect.cleandoctak:

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

Względne wcięcie zostanie zachowane zgodnie z oczekiwaniami. Jak skomentowano poniżej, jeśli chcesz zachować poprzedzające puste linie, użyj textwrap.dedent. Zachowuje to jednak także pierwszą linię podziału.

Uwaga: Dobrą praktyką jest wcięcie logicznych bloków kodu w powiązanym kontekście w celu wyjaśnienia struktury. Np. Ciąg wielu linii należący do zmiennej string.

wihlke
źródło
5
Tak bardzo zdezorientowany, dlaczego ta odpowiedź nie istniała do tej pory, inspect.cleandocistnieje od czasu Python 2.6 , który był w 2008 roku ..? Absolutnie najczystsza odpowiedź, szczególnie dlatego, że nie używa stylu wiszącego wcięcia, które po prostu marnuje niepotrzebną ilość miejsca
Kevlarr
1
To rozwiązanie usuwa kilka pierwszych wierszy pustego tekstu (jeśli występują). Jeśli nie chcesz tego zachowania, użyj textwrap.dedent docs.python.org/2/library/textwrap.html#textwrap.dedent
joshuakcockrell
1
To jest doskonałe!
zzzz zzzz
23

Jedną z opcji, która wydaje się brakować w innych odpowiedziach (wspomnianych tylko głęboko w komentarzu naxa), jest następująca:

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

Pozwoli to na prawidłowe wyrównanie, niejawne łączenie linii i nadal będzie zachowywać przesunięcie linii, co dla mnie jest jednym z powodów, dla których i tak chciałbym używać ciągów wielowierszowych.

Nie wymaga żadnego postprocessingu, ale musisz ręcznie dodać \nw dowolnym miejscu, w którym chcesz zakończyć linię. Albo inline, albo jako osobny ciąg po. Ten ostatni łatwiej jest skopiować i wkleić.

holroy
źródło
Zauważ, że jest to przykład niejawnie połączonego łańcucha, a nie łańcucha wielowierszowego.
trk
@trk, jest wielowierszowy w tym sensie, że ciąg zawiera nowe linie (inaczej wiele linii), ale tak, używa łączenia, aby obejść problemy z formatowaniem OP.
holroy,
17

Więcej opcji. W Ipython z włączoną funkcją pylab dedent jest już w przestrzeni nazw. Sprawdziłem i pochodzi z matplotlib. Lub można go zaimportować za pomocą:

from matplotlib.cbook import dedent

W dokumentacji stwierdza się, że jest on szybszy niż ekwipunek do zawijania tekstu, aw moich testach w ipython jest on średnio 3 razy szybszy w moich szybkich testach. Ma także tę zaletę, że odrzuca wszelkie puste puste linie, co pozwala na elastyczność w tworzeniu łańcucha:

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

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

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

Użycie matplotlib dedent na tych trzech przykładach da ten sam rozsądny rezultat. Funkcja pochylania tekstu będzie miała wiodący pusty wiersz z pierwszym przykładem.

Oczywistą wadą jest to, że zawijanie tekstu znajduje się w standardowej bibliotece, podczas gdy matplotlib jest modułem zewnętrznym.

Niektóre kompromisy tutaj ... funkcje dedent sprawiają, że kod jest bardziej czytelny tam, gdzie definiowane są ciągi, ale wymagają przetworzenia później, aby uzyskać ciąg w użytecznym formacie. W dokumentach jest oczywiste, że należy używać poprawnego wcięcia, ponieważ większość zastosowań dokumentów będzie wymagała przetwarzania.

Kiedy potrzebuję długiego łańcucha w kodzie, znajduję następujący, co prawda brzydki kod, w którym pozwalam, by długi łańcuch wypadł z otaczającego wcięcia. Zdecydowanie zawodzi „Piękne jest lepsze niż brzydkie”, ale można argumentować, że jest prostsze i bardziej wyraźne niż wydana alternatywa.

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
źródło
6

Jeśli chcesz szybkiego i łatwego rozwiązania i oszczędzasz sobie pisania nowych linii, możesz zamiast tego wybrać listę, np .:

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
źródło
Chociaż nie jest to najlepsze podejście, korzystałem z niego od czasu do czasu. Jeśli go użyjesz, powinieneś użyć krotki zamiast listy, ponieważ nie zostanie zmodyfikowana przed dołączeniem.
Lyndsy Simon
4

wolę

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

lub

    def method():
        string = """\
line one
line two
line three\
"""
lk_vc
źródło
1
To nie odpowiada na pytanie, ponieważ pytanie wyraźnie stwierdza, że ​​wcięcie (w ramach funkcji) ma znaczenie.
bignose
@bignose Pytanie brzmiało: „Wygląda to trochę dziwnie”, którego nie wolno używać.
lk_vc
jak miałbym to osiągnąć bez brzydkiego wcięcia?
lfender6445,
@ lfender6445 cóż, być może możesz umieścić wszystkie te ciągi w osobnym pliku od innych kodów ...
lk_vc 10.10.17
3

Moje dwa centy, uniknij końca linii, aby uzyskać wcięcia:

def foo():
    return "{}\n"\
           "freq: {}\n"\
           "temp: {}\n".format( time, freq, temp )
Szymon
źródło
1

Przybyłem tutaj, szukając prostej 1-liniowej linijki do usuwania / poprawiania poziomu identyfikacji dokumentów do drukowania, bez powodowania niechcianego wyglądu , na przykład poprzez „zawieszenie się poza funkcją” w skrypcie.

Oto co skończyłem:

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

Oczywiście, jeśli wcinasz spacje (np. 4) zamiast klawisza tab, użyj czegoś takiego:

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

I nie musisz usuwać pierwszego znaku, jeśli podoba Ci się twój ciąg dokumentów, aby wyglądał w ten sposób:

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

print string.replace(myfunction.__doc__,'\n\t','\n') 
James Gowdy
źródło
Nie udaje się to w przypadku metod klas i klas zagnieżdżonych.
tacaswell
1

Pierwsza opcja jest dobra - z uwzględnieniem wcięcia. Jest w stylu python - zapewnia czytelność kodu.

Aby wyświetlić go poprawnie:

print string.lstrip()
Bog Dia
źródło
To wydaje się być najprostszym i najczystszym sposobem formatowania potrójnych ciągów cytatów, dzięki czemu nie masz dodatkowych spacji z powodu wcięcia
Taylor Liss
4
Spowoduje to usunięcie tylko wiodących spacji w pierwszym wierszu ciągu wielowierszowego. Nie pomaga w formatowaniu następujących wierszy.
M. Schlenker,
0

Zależy to od sposobu wyświetlania tekstu. Jeśli chcesz, aby wszystko było wyrównane do lewej, sformatuj go jak w pierwszym fragmencie lub przejdź przez linie przycinające w lewo całą przestrzeń.

Ignacio Vazquez-Abrams
źródło
5
Narzędzia do przetwarzania dokumentów działają w ten sposób, że nie usuwają całego miejsca po lewej stronie, ale tyle co pierwsza linia wcięcia. Ta strategia jest nieco bardziej wyrafinowana i pozwala na wcięcie i przestrzeganie jej w łańcuchu przetwarzania końcowego.
Mike Graham
0

W przypadku ciągów możesz zaraz po przetworzeniu ciągu. W przypadku dokumentów należy przetworzyć funkcję zamiast tego. Oto rozwiązanie dla obu, które jest nadal czytelne.

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__
gekony
źródło
1
Przetwarzanie dokumentów musi już przetwarzać spójne wcięcie, jak opisano w PEP 257 . Istnieją już narzędzia - np. inspect.cleandoc- które robią to we właściwy sposób.
bignose
0

Mam podobny problem, kod stał się naprawdę nieczytelny przy użyciu multilinii, wyszło mi coś takiego

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

tak, na początku może wyglądać okropnie, ale wbudowana składnia była dość złożona, a dodanie czegoś na końcu (np. „\ n” ”) nie było rozwiązaniem

Frediano Ziglio
źródło
0

Możesz użyć tej funkcji 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


"""))

Wynik:

"""
line one
    line two
        line three
    line two
line one
"""
Luan Silveira
źródło