Правильний відступ для багаторядкових рядків 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 для таких цілей, як визначення функцій та довгий, якщо оператори, хоча і не згадуються для рядкових рядків. Особисто це одне місце, я відмовляюся дотримуватися PEP8 (і замість цього використовую 4-пробільні відступи), оскільки сильно не люблю висячі відступи, які для мене затьмарюють належну структуру програми.
bobince

2
@buffer, в 3.1.2 офіційного підручника ("Два рядкових літералі поруч один з одним автоматично з'єднуються ...") та в мовній мові.
Майк Грехем

5
Друга форма з автоматичним конкатенацією рядків не включає новий рядок. Це особливість.
Майк Грехем

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

2
Коментар від +1 до @bobince про відхилення тут "висячих відступів" ... Тим більше, що якщо ви зміните ім'я змінної з stringна textабо інше різної довжини, то вам тепер потрібно оновити відступ буквально кожного окремого рядка багаторядковий рядок просто для того, щоб він відповідав """належним чином. Стратегія відступу не повинна ускладнювати майбутні рефактори / обслуговування, і це одне з місць, яким PEP справді не вдається
kevlarr

253

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)

Трейлінг \у літеральному повідомленні журналу полягає у тому, щоб переривання рядків не було в прямому; таким чином, літерал не починається з порожнього рядка, а замість цього починається з наступного повного рядка.

Зворотним значенням textwrap.dedentє вхідна рядок із усіма загальними відступами провідного пробілу, видаленими у кожному рядку рядка. Отже, наведене вище log_messageзначення буде:

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

1
Хоча це розумне рішення і приємно знати, робити щось подібне всередині часто називається функції може виявитися катастрофою.
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
Я думаю, це було б неефективно, якщо рядок призначений для ведення журналу, який увімкнено лише в режимі налагодження, і не використовується невикористаним. Але тоді навіщо взагалі записувати багаторядковий літеральний рядок? Тому важко знайти приклад із реального життя, де вищезазначене було б неефективним (тобто там, де це значно уповільнює програму), тому що все, що споживає ці рядки, буде повільніше.
Євгеній Сергєєв

52

Використовуйте 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, це багаторядковий, в тому сенсі, що рядок містить нові рядки (він же декілька рядків), але так, він використовує приєднання, щоб обійти проблеми форматування, які мали ОП.
holroy

17

Ще кілька варіантів. У програмі Ipython із включеним pylab дедент вже знаходиться в просторі імен. Я перевірив, і це від matplotlib. Або його можна імпортувати за допомогою:

from matplotlib.cbook import dedent

У документації йдеться про те, що він швидший, ніж еквівалентний текстовому обгортанні, а в моїх тестах на 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 на цих трьох прикладах дасть однаковий розумний результат. Функція виділення текстового обгортання матиме провідний порожній рядок із 1-м прикладом.

Очевидним недоліком є ​​те, що текстове обговорення знаходиться в стандартній бібліотеці, тоді як matplotlib є зовнішнім модулем.

Деякі компроміси тут ... присвячені функції роблять ваш код більш читабельним, коли рядки визначаються, але потребують опрацювання пізніше, щоб отримати рядок у використаному форматі. У документах очевидно, що ви повинні використовувати правильний відступ, оскільки більшість застосувань docstring виконають необхідну обробку.

Коли мені потрібен недовгий рядок у моєму коді, я знаходжу наступний негарно некрасивий код, у якому я дозволяю довгому рядку випасти із вкладеного відступу. Однозначно виходить з ладу "Красиве - це краще, ніж потворне". Але можна стверджувати, що воно простіше і явніше, ніж прихильна альтернатива.

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

І вам не потрібно видаляти перший символ, якщо вам подобається, що ваші docstrings виглядають так:

    """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.