正确缩进Python多行字符串


455

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

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

要么

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

或者是其他东西?

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


4
文档字符串被特殊对待:第一行的任何缩进都将被删除;所有其他非空白行上最小的最小公共缩进全部从它们中删除。除此之外,不幸的是,Python中的多行字符串文字是您在空格中看到的:字符串定界符之间的所有字符都成为字符串的一部分,包括缩进,即使用Python读取本能时,看起来应该从字面量开始的行的缩进量来衡量。
Evgeni Sergeev

@EvgeniSergeev处理工具执行此任务(这在很大程度上取决于您对处理工具的选择)。method.__doc__本身并没有被Python修改过str
CZ

Answers:


453

您可能想与 """

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

由于换行符和空格包含在字符串本身中,因此您必须对其进行后处理。如果您不想这样做,并且文本很多,则可能需要将其分别存储在文本文件中。如果文本文件不能很好地适合您的应用程序,并且您不想进行后处理,那么我可能会选择

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

如果要对多行字符串进行后处理以修剪掉不需要的部分,则应考虑PEP 257中textwrap介绍的对文档字符串进行后处理的模块或技术:

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中对它进行了规定,以实现功能定义和long if语句之类的目的,尽管多行字符串未提及。就我个人而言,这是我拒绝遵循PEP8(使用4空间缩进)的地方,因为我非常不喜欢悬挂式缩进,这对我来说模糊了程序的正确结构。
bobince 2010年

2
@buffer,在官方教程的3.1.2中(“两个字符串文字彼此自动连接...”)和语言参考中的内容。
Mike Graham

5
具有自动字符串连接的第二种形式不包括换行符这是一个功能。
Mike Graham

18
trim()PEP257中指定的功能在标准库中以形式实现inspect.cleandoc

2
+1到@bobince的有关此处拒绝“悬挂缩进”的注释……尤其是因为如果您将变量名从更改stringtext或长度不同的任何东西,那么现在您需要更新字面上的每一行的缩进多行字符串只是为了使其"""正确匹配。缩进策略不应使将来的重构/维护复杂化,这是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)

\日志消息文字中的结尾是为了确保换行符不在文字中;这样,文字不以空白行开头,而是以下一个完整行开头。

from的返回值textwrap.dedent是输入字符串,在字符串的每一行上都删除所有常见的前导空格。因此,上面的log_message值将是:

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

1
尽管这是一个合理的解决方案,但很高兴知道,但是在一个经常调用的函数中执行此类操作可能会造成灾难。
haridsv 2011年

@haridsv为什么那会是一场灾难?
jtmoulia 2012年

10
@jtmoulia:比灾难更好的描述是“低效的”,因为textwrap.dedent()调用的结果是一个常数值,就像其输入参数一样。
martineau'8年

2
@haridsv灾难/低效率的根源经常调用的函数定义一个常量字符串。可以将每次调用常量定义换成每次调用查找。这样,暂定的预处理将运行一次。一个相关的问题可能是stackoverflow.com/q/15495376/611007它列出了一些想法,以避免为每个调用定义常量。尽管替代方案似乎需要查找。尽管如此,尝试了各种寻找合适的存储位置的方法。例如:def foo: return foo.x然后下一行foo.x = textwrap.dedent("bar")
n611x007

1
我猜想,如果该字符串仅用于调试模式,则该字符串仅用于日志记录,否则无效。但是,为什么仍然要记录多行字符串文字呢?因此,很难找到一个现实的例子,上面的例子效率不高(即,这会大大降低程序的速度),因为消耗这些字符串的任何事情都会变慢。
Evgeni Sergeev

52

inspect.cleandoc像这样使用:

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

相对缩进将保持预期。正如评论下面,如果你想保持使用前空行,textwrap.dedent。但是,这样也可以保持第一行。

注意:优良作法是在代码的相关上下文下缩进逻辑代码块以阐明结构。例如,属于变量的多行字符串string


5
如此困惑,为什么这个答案直到现在都还不存在,inspect.cleandocPython 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的格式问题。
holroy

17

一些更多的选择。在启用pylab的Ipython中,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将产生相同的明智结果。textwrap dedent函数在第一个示例中将有一个前导空白行。

明显的缺点是textwrap在标准库中,而matplotlib是外部模块。

这里有一些折衷... 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

虽然这不是最好的方法,但我不时使用它。如果确实使用它,则应该使用元组而不是列表,因为在加入之前不会对其进行修改。
Lyndsy Simon

4

我更喜欢

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

要么

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

1
这不能回答问题,因为问题明确指出缩进(在函数内)很重要。
bignose

@bignose问题不允许使用“看起来有点奇怪”。
lk_vc '17

没有丑陋的缩痕,我将如何做到这一点?
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)而不是Tab键,请改用如下代码:

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 2014年

1

第一种选择是好的-包括缩进。它是python样式-提供代码的可读性。

要正确显示它:

print string.lstrip()

这似乎是格式化三引号字符串的最简单,最干净的方法,因此,由于缩进,您不会有多余的空格
Taylor Liss,

4
这只会删除多行字符串第一行中的前导空格。它不利于格式化以下几行。
M. Schlenker,

0

这取决于您希望文本如何显示。如果您希望所有内容都左对齐,则可以按照第一个代码段的格式对其进行格式化,也可以遍历所有空间的左行进行迭代。


5
该方法文档字符串处理工具的工作是不删除所有左边的空间,但尽可能多的第一凹入线。该策略稍微复杂一点,可以让您缩进并在后处理的字符串中得到尊重。
Mike Graham

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
"""
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.