Правильный отступ для многострочных строк Python


456

Каков правильный отступ для многострочных строк Python внутри функции?

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

или

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

или что-то другое?

В первом примере выглядит странно, что строка висит вне функции.


4
Строки документов обрабатываются специально : любой отступ первой строки удаляется; наименьший общий отступ, взятый над всеми непустыми строками, удаляется из них всех. Кроме этого, многострочные строковые литералы в Python, к сожалению, «что видишь, то, что ты получаешь» в терминах пробелов: все символы между разделителями строк становятся частью строки, включая отступы, которые с помощью инстинктов чтения Python, Похоже, это должно быть измерено от отступа строки, где начинается литерал.
Евгений Сергеев

@EvgeniSergeev Инструмент обработки выполняет эту задачу (и это во многом зависит от вашего выбора инструмента обработки). method.__doc__не изменяется самим Python больше, чем любой другой strлитерал.
cz

Ответы:


453

Вы, вероятно, хотите соответствовать """

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

Так как символы новой строки и пробелы включены в саму строку, вам придется постобработать ее. Если вы не хотите этого делать, и у вас есть много текста, вы можете сохранить его отдельно в текстовом файле. Если текстовый файл не подходит для вашего приложения и вы не хотите выполнять постобработку, я бы, вероятно, пошел с

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

Если вы хотите постобработать многострочную строку, чтобы обрезать ненужные вам части, вы должны рассмотреть textwrapмодуль или технику постобработки строк документации, представленную в 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
Это стиль продолжения строки «висячий отступ». Это предписано в PEP8 для таких целей, как определения функций и длинные операторы if, хотя и не упоминается для многострочных строк. Лично это то место, где я отказываюсь следовать PEP8 (и вместо этого использую отступ в 4 пробела), так как мне сильно не нравятся висячие отступы, которые для меня затеняют правильную структуру программы.
bobince

2
@buffer, в 3.1.2 официального руководства («Два строковых литерала рядом друг с другом автоматически объединяются ...») и в справочнике по языку.
Майк Грэм,

5
Вторая форма с автоматической конкатенацией строк не включает перевод строки. Это особенность.
Майк Грэм

19
trim()Функция , как указано в PEP257 реализован в стандартной библиотеке как inspect.cleandoc.

2
+1 к комментарию @bobince об отклонении «висячих отступов» здесь ... Тем более, что если вы изменяете имя переменной с stringна textили что-то другое, то теперь вам нужно обновить отступ буквально для каждой строки многострочная строка только для того, чтобы она соответствовала """правильно. Стратегия отступов не должна усложнять будущие рефакторинг / обслуживание, и это одно из мест, где PEP действительно терпит неудачу
kevlarr

255

textwrap.dedentФункция позволяет начать с правильным отступом в источнике , а затем лишить его из текста перед использованием.

Как отмечают некоторые другие, компромисс заключается в том, что это дополнительный вызов функции для литерала; примите это во внимание при решении, где разместить эти литералы в вашем коде.

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)

Завершающий конец \в литерале сообщения журнала должен гарантировать, что разрыв строки не находится в литерале; таким образом, литерал не начинается с пустой строки, а начинается со следующей полной строки.

Возвращаемое значение from textwrap.dedentявляется входной строкой со всеми общими начальными пробелами, удаленными в каждой строке строки. Таким образом, вышеуказанное log_messageзначение будет:

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

2
Хотя это разумное решение и приятно знать, выполнение чего-то подобного внутри часто вызываемой функции может оказаться катастрофой.
haridsv

@haridsv Почему это будет катастрофа?
jtmoulia

10
@jtmoulia: лучшее описание, чем катастрофа, было бы «неэффективным», потому что результатом textwrap.dedent()вызова является постоянное значение, как и его входной аргумент.
Мартино

2
@haridsv источник этой катастрофы / неэффективности определяет постоянную строку внутри часто вызываемой функции. Возможно обменять определение константы для каждого вызова на поиск для каждого вызова. Таким образом, предварительная обработка отступа будет выполняться только один раз . Соответствующий вопрос может быть stackoverflow.com/q/15495376/611007 В нем перечислены идеи, чтобы избежать определения константы для каждого вызова. Хотя альтернативы, кажется, требуют поиска. Тем не менее, предпринимаются различные способы найти подходящее место для его хранения. Например: def foo: return foo.xтогда следующая строка foo.x = textwrap.dedent("bar").
n611x007

1
Я думаю, это было бы неэффективно, если строка предназначена для ведения журнала, которая включена только в режиме отладки, и в противном случае не используется. Но тогда зачем записывать многострочный строковый литерал? Поэтому трудно найти реальный пример, где вышеприведенное было бы неэффективно (то есть, где это значительно замедляет программу), потому что все, что потребляет эти строки, будет работать медленнее.
Евгений Сергеев

53

Используйте inspect.cleandocтак:

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

Относительный отступ будет сохранен как ожидалось. Как указано ниже, если вы хотите сохранить предыдущие пустые строки, используйте textwrap.dedent. Однако это также сохраняет разрыв первой строки.

Примечание. Рекомендуется делать отступы логических блоков кода в соответствующем контексте, чтобы прояснить структуру. Например, многострочная строка, принадлежащая переменной string.


5
Так растерянно, почему этот ответ не существовал до сих inspect.cleandocпор, существует с Python 2.6 , который был в 2008 году ..? Абсолютно чистый ответ, особенно потому, что он не использует стиль висячего отступа, который просто тратит ненужное количество места
kevlarr

1
Это решение удаляет первые несколько строк пустого текста (если есть). Если вы не хотите, чтобы такое поведение, используйте textwrap.dedent docs.python.org/2/library/textwrap.html#textwrap.dedent
joshuakcockrell

1
Это потрясающе!
zzzz zzzz

23

Один из вариантов, который, по-видимому, отсутствует в других ответах (упоминается только в комментариях naxa):

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

Это позволит правильно выравнивать, неявно соединять линии и сохранять сдвиг строк, что для меня является одной из причин, по которой я все равно хотел бы использовать многострочные строки.

Это не требует никакой постобработки, но вам нужно вручную добавить \nв любом месте, где вы хотите, чтобы строка заканчивалась. Либо в строке или в виде отдельной строки после. Последний легче скопировать-вставить в.


Обратите внимание, что это пример неявно объединенной строки, а не многострочной строки.
TRK

@trk, это многострочный текст в том смысле, что строка содержит новые строки (или несколько строк), но да, она использует объединение, чтобы обойти проблемы форматирования, которые были у OP.
Холрой

17

Еще несколько вариантов. В Ipython с включенным pylab dedent уже находится в пространстве имен. Я проверил, и это из Matplotlib. Или это может быть импортировано с:

from matplotlib.cbook import dedent

В документации говорится, что она быстрее, чем эквивалентная textwrap, и в моих тестах на ipython она действительно в среднем в 3 раза быстрее с моими быстрыми тестами. Он также имеет то преимущество, что отбрасывает все начальные пустые строки, что позволяет вам быть гибким в том, как вы строите строку:

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

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

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

Использование matplotlib dedent в этих трех примерах даст тот же разумный результат. Функция dedent textwrap будет иметь начальную пустую строку в первом примере.

Очевидным недостатком является то, что textwrap находится в стандартной библиотеке, а matplotlib - внешний модуль.

Некоторые компромиссы здесь ... функции dedent делают ваш код более читабельным, когда определяются строки, но требуют обработки позже, чтобы получить строку в пригодном для использования формате. В строках документации очевидно, что вы должны использовать правильный отступ, так как большинство использований строки документации выполнят требуемую обработку.

Когда мне нужна недлинная строка в моем коде, я нахожу следующий заведомо уродливый код, где я позволяю длинной строке выпасть из вмещающего отступа. Определенно терпит неудачу на «Красивое лучше, чем некрасивое.», Но можно утверждать, что это проще и более явно, чем альтернатива dedent.

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

Если вы хотите быстрое и простое решение и избавите себя от ввода новых строк, вместо этого вы можете выбрать список, например:

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

Хотя это не лучший подход, я использовал его время от времени. Если вы делаете его использовать, вы должны использовать кортеж вместо списка, так как он не будет изменен перед соединением.
Линдси Симон

4

я предпочитаю

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

или

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

1
Это не отвечает на вопрос, потому что в вопросе прямо говорится, что отступ (внутри функции) имеет значение.
bignose

@bignose В вопросе говорилось: «Это выглядит странно», не разрешено использовать.
lk_vc

Как бы я сделал это без уродливого отступа?
lfender6445

@ lfender6445 ну, может быть, вы можете поместить все эти строки в отдельный файл из других кодов ...
lk_vc

3

Мои два цента, выходите из конца строки, чтобы получить отступы:

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

1

Я пришел сюда в поисках простой 1- строчной строки для удаления / исправления уровня выделения строки документации для печати, не делая его неопрятным , например, заставляя его «висеть вне функции» в скрипте.

Вот что я в итоге сделал:

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

Очевидно, что если вы делаете отступ с пробелами (например, 4), а не с клавишей табуляции, используйте что-то вроде этого:

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

И вам не нужно удалять первый символ, если вы хотите, чтобы строки документации выглядели так:

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

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

Это терпит неудачу на методах класса и вложенных классах.
tacaswell

1

Первый вариант - хороший, с отступом. Это в стиле Python - обеспечивает читабельность кода.

Чтобы отобразить это правильно:

print string.lstrip()

Это кажется самым простым и чистым способом форматирования строк с тройными кавычками, чтобы у вас не было лишних пробелов из-за отступов
Тейлор Лисс

4
Это удалит только первые пробелы в первой строке многострочной строки. Это не помогает с форматированием следующих строк.
М.

0

Это зависит от того, как вы хотите, чтобы текст отображался. Если вы хотите, чтобы все было выровнено по левому краю, то либо отформатируйте его, как в первом фрагменте, либо переберите все строки, обрезая влево все пространство.


5
Способ обработки строки документации инструменты работа , чтобы удалить не все пространство на левой сторону , но так же , как первый отступа линии. Эта стратегия немного сложнее и позволяет вам делать отступы и уважать их в постобработанной строке.
Майк Грэм,

0

Для строк вы можете сразу после обработки строки. Для строк документации вам нужно обработать функцию. Вот решение для обоих, которое все еще читаемо.

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
Обработка строк документации уже должна обрабатывать согласованные отступы, как описано в PEP 257 . Уже есть инструменты, например, inspect.cleandocкоторые делают это правильно.
bignose

0

У меня похожая проблема, код стал действительно нечитаемым с помощью мультилиней, я вышел с чем-то вроде

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

да, в начале это могло выглядеть ужасно, но встроенный синтаксис был довольно сложным, и добавление чего-либо в конце (например, \ n ") не было решением


0

Вы можете использовать эту функцию 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


"""))

Результат:

"""
line one
    line two
        line three
    line two
line one
"""
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.