多行字符串的正确缩进方式?

606

在Python函数中,多行字符串的正确缩进是什么?

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

还是其他什么?

在第一个示例中,字符串悬挂在函数外部看起来有点奇怪。


10
Docstrings被特殊处理:第一行的任何缩进都将被移除;所有其他非空行中最小的公共缩进将从它们所有行中移除。除此之外,在Python中多行字符串字面值不幸地保持了所见即所得的空格格式:字符串分隔符之间的所有字符都成为字符串的一部分,包括看起来应该从字面值开始行的缩进。 - Evgeni Sergeev
3
这个处理工具执行这项任务(这在很大程度上取决于你选择的处理工具)。method.__doc__ 和任何其他 str 字面值一样,并不会被 Python 自身修改。 - c z
12个回答

529
你可能想要与 """ 对齐。
def foo():
    string = """line one
             line two
             line three"""

由于换行和空格包含在字符串本身中,您将需要进行后处理。如果您不想这样做,并且有大量的文本,您可能希望将其单独存储在一个文本文件中。如果文本文件对您的应用程序不起作用,并且您不想进行后处理,我可能会选择...
def foo():
    string = ("this is an "
              "implicitly joined "
              "string")

如果你想对多行字符串进行后处理,以去除不需要的部分,你应该考虑使用textwrap模块或者在PEP 257中介绍的处理文档字符串的技术。
def trim(docstring):
    import sys
    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)

16
这是“悬挂缩进”换行的格式。PEP8中规定了这种格式用于函数定义和长if语句等场景,但未提及用于多行字符串。就个人而言,这是我拒绝遵循PEP8的一点(而改用4空格缩进),因为我非常不喜欢悬挂缩进,它让程序的结构变得不清晰。 - bobince
2
@buffer,在官方教程的3.1.2节中(“相邻的两个字符串字面量会自动连接在一起...”),以及语言参考中都有相关说明。 - Mike Graham
5
第二种带有自动字符串连接的形式不包括换行符。这是一个特性。 - Mike Graham
31
PEP257 中规定的 trim() 函数在标准库中被实现为 inspect.cleandoc - user816328
5
关于拒绝“悬挂缩进”,我赞同@bobince的评论...特别是因为如果您将变量名从 string 更改为 text 或其他长度不同的名称,那么您现在需要更新多行字符串的每一行缩进才能使其正确地匹配 """。 缩进策略不应该使未来的重构/维护变得复杂,这也是PEP失败的地方之一。 - kevlarr
显示剩余10条评论

377

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!

5
尽管这是一个合理的解决方案,也很有用,但在经常被调用的函数内执行此类操作可能会导致灾难性后果。 - haridsv
3
@haridsv 为什么会是一场灾难? - jtmoulia
14
比起“灾难”,更好的描述应该是“低效”,因为textwrap.dedent()函数调用的结果与输入参数一样都是一个常量值。 - martineau
4
@haridsv 那场灾难/低效的起因是在一个频繁被调用的函数内定义了一个常量字符串。可以通过每次调用时进行常量查找来替代每次调用时的常量定义。这样,缩排预处理只运行一次。一个相关的问题可能是https://dev59.com/ZGUp5IYBdhLWcg3wHk2o。它列出了避免对每个调用定义常量的想法。尽管备选方案似乎需要进行查找。仍然有各种方法来寻找适合存储它的位置。例如:`def foo: return foo.x然后下一行foo.x = textwrap.dedent("bar")`。 - n611x007
1
我想,如果字符串仅在调试模式下启用且在其他情况下未使用,则其效率会很低。但是,为什么要记录多行字符串文字呢?因此很难找到真实生活中的一个例子,在这种情况下效率会降低(即它会显着减慢程序),因为无论使用这些字符串的任何内容都会变慢。 - Evgeni Sergeev

166
使用inspect.cleandoc的方式如下:
import inspect

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

相对缩进将按预期保持不变。如下所述,如果您想保留前面的空行,请使用textwrap.dedent。但是这也会保留第一个换行符。
注意:将代码的逻辑块缩进到相关上下文中以澄清结构是一种良好的实践。例如,属于变量string的多行字符串。

11
为什么这个答案到现在为止还不存在,inspect.cleandocPython 2.6 开始就存在了,那是在 2008。这绝对是最干净的答案,特别是它不使用悬挂缩进风格,这只会浪费不必要的空间。 - kevlarr
2
这个解决方案会删除前几行的空白文本(如果有的话)。如果您不想要这种行为,请使用textwrap.dedent。https://docs.python.org/2/library/textwrap.html#textwrap.dedent - joshuakcockrell
示例文档建议“在第一行结尾处使用\以避免空行!”,这解决了上述提到的“保留第一个换行符”的问题。https://docs.python.org/3/library/textwrap.html - autopoietic

40

其他答案好像都没有提到 (只有 naxa 在评论中深入提到) 的一个选项是:

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

这将允许正确对齐、隐式地连接行,并仍然保留换行符号,对我来说,这是使用多行字符串的原因之一。

它不需要任何后处理,但您需要在任何需要换行的位置手动添加 \n。可以内联或作为单独的字符串添加。后者更容易复制粘贴。


6
请注意,这是一个隐式连接字符串的示例,而不是多行字符串。 - trk
2
@trk,它是多行的意思是字符串包含换行符(也称为多行),但是它确实使用连接来规避OP遇到的格式问题。 - holroy
1
这对我来说看起来是最好的答案。但到目前为止,我不明白为什么Python需要三引号运算符,如果它们会导致难以阅读的代码。 - klm123
现在我只需要考虑如何返回一个多行字符串字面值。我不想通过后处理或其他方式来支持它们,我只希望字面值能够读起来很好。这个版本是迄今为止最简单和最易读的,当使用black格式化时(第一个字符串段落在新行上,并在括号之后的下一个缩进处对齐),比答案中显示的效果更好,可以为更长的字符串提供更多的行长度。 - undefined

21

更多选项。在启用pylab的Ipython中,dedent已经在命名空间中了。我检查过它是来自matplotlib的。或者可以使用以下方式导入:

一些更多的选项。在启用pylab的Ipython中,dedent已经被引入命名空间。我检查过它是由matplotlib提供的。或者可以通过以下方式导入:

from matplotlib.cbook import dedent

文档中指出它比textwrap的等效物更快,在我的IPython测试中,平均确实比textwrap快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函数会给这三个例子带来相同的合理结果。textwrap的dedent函数将在第一个例子中有一个前导空行。

显而易见的缺点是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()

11

如果您想要一个快速且方便的解决方案,并且不想要自己输入换行符号,那么您可以选择使用列表,例如:

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

2
虽然这不是最好的方法,但我有时会使用它。如果您确实使用它,应该使用元组而不是列表,因为在连接之前它不会被修改。 - Lyndsy Simon
请注意,如果打印的目的是为了更清晰地显示内容,那么在打印语句中将sep参数设置为'\n'比使用join函数更加简洁。 - undefined

4

I prefer

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

或者

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

3
这并没有回答问题,因为问题明确指出缩进(在函数内部)很重要。 - bignose
1
@bignose 这个问题说“看起来有点奇怪”,并没有禁止使用。 - lk_vc
1
我该如何在不使用丑陋的缩进的情况下完成这个任务? - lfender6445
@lfender6445 嗯,也许你可以将所有这些字符串放到一个与其他代码分开的单独文件中... - lk_vc

3

我的建议是,为了获得缩进,请转义换行符:

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

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

1
这在类方法和嵌套类上失败。 - tacaswell

0
第一个选项是好的 - 包括缩进。它采用Python风格 - 为代码提供可读性。
要正确显示它:
print string.lstrip()

这似乎是格式化三引号字符串的最简单和最清洁的方法,因此您不会因缩进而产生额外的空格。 - Taylor Liss
14
这将只会删除多行字符串第一行前导的空格,无法帮助格式化后续行。 - M. Schlenker

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接