【问题标题】:Proper indentation for multiline strings?多行字符串的正确缩进?
【发布时间】:2011-01-31 01:21:44
【问题描述】:

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

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

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

还是别的什么?

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

【问题讨论】:

  • 文档字符串被处理specially:第一行的任何缩进都被删除;占据所有其他非空行的最小公共缩进将从它们中全部删除。除此之外,不幸的是,Python 中的多行字符串文字在空格方面是所见即所得:字符串分隔符之间的所有字符都成为字符串的一部分,包括缩进,Python 阅读本能,看起来它应该从文字开始的行的缩进开始测量。
  • @EvgeniSergeev 处理工具执行此任务(这在很大程度上取决于您选择的处理工具)。 method.__doc__ 不会被 Python 本身修改,而不是任何其他 str 文字。

标签: python string


【解决方案1】:

你可能想排队"""

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)

【讨论】:

  • 这是续行的“悬挂缩进”样式。它在 PEP8 中被规定用于函数定义和长 if 语句等目的,但未针对多行字符串提及。就个人而言,这是我拒绝遵循 PEP8 的一个地方(而是使用 4 空格缩进),因为我非常不喜欢悬挂缩进,这对我来说会模糊程序的正确结构。
  • @buffer,在官方教程的3.1.2(“两个相邻的字符串文字会自动连接...”)和语言参考中。
  • 第二种自动串接的形式不包含换行符这是一个特性。
  • PEP257 中指定的trim() 函数在标准库中实现为inspect.cleandoc
  • +1 到 @bobince 关于在此处拒绝“悬挂缩进”的评论...特别是因为如果您将变量名称从 string 更改为 text 或任何其他长度,那么您现在需要更新多行字符串的每一行 的缩进,以使其与""" 正确匹配。缩进策略不应使未来的重构/维护复杂化,这是 PEP 真正失败的地方之一
【解决方案2】:

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!

【讨论】:

  • 虽然这是一个合理的解决方案并且很高兴知道,但在经常调用的函数中执行这样的操作可能会证明是一场灾难。
  • @haridsv 为什么会是一场灾难?
  • @jtmoulia:比灾难更好的描述是“低效”,因为textwrap.dedent() 调用的结果是一个常量值,就像它的输入参数一样。
  • @haridsv 灾难/低效率的根源是定义一个常量字符串内部一个经常调用的函数。可以将每次调用常量定义换成每次调用查找。这样,dedent 预处理将运行一次。一个相关的问题可能是stackoverflow.com/q/15495376/611007 它列出了避免每次调用都定义常量的想法。尽管替代方案似乎需要查找。尽管如此,仍尝试了各种方法来找到存储它的有利位置。例如:def foo: return foo.x 然后下一行foo.x = textwrap.dedent("bar")
  • 我想如果该字符串仅用于在调试模式下启用的日志记录,并且在其他情况下不使用,那将是低效的。但是,为什么还要记录多行字符串文字呢?所以很难找到一个现实生活中的例子,上面的方法效率低下(即它大大减慢了程序的速度),因为任何消耗这些字符串的东西都会变慢。
【解决方案3】:

像这样使用inspect.cleandoc

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

将按预期保持相对缩进。如下面的commented,如果要保留前面的空行,请使用textwrap.dedent。但是,这也保留了第一个换行符。

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

【讨论】:

  • 很困惑为什么这个答案直到现在才存在,inspect.cleandoc 自从Python 2.6 就已经存在,即2008..?绝对是最干净的答案,特别是因为它不使用悬挂缩进样式,这只会浪费不必要的空间
  • 此解决方案会删除前几行空白文本(如果有)。如果您不想要这种行为,请使用 textwrap.dedent docs.python.org/2/library/textwrap.html#textwrap.dedent
【解决方案4】:

其他答案中似乎缺少的一个选项(仅在 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,它是多行的,因为字符串包含换行符(又名多行),但是是的,它使用连接来规避 OP 的格式问题。
  • 这对我来说似乎是最好的答案。但到目前为止,我不明白为什么 python 需要三引号运算符,如果它们导致难以阅读的代码。
【解决方案5】:

更多选择。在启用了 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
    

    【讨论】:

    • 虽然这不是最好的方法,但我经常使用它。如果你确实使用它,你应该使用元组而不是列表,因为它在加入之前不会被修改。
    【解决方案7】:

    我更喜欢

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

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

    【讨论】:

    • 这没有回答问题,因为问题明确指出缩进(在函数内)很重要。
    • @bignose 问题是“看起来有点奇怪”,不允许使用。
    • 如果没有丑陋的缩进,我将如何做到这一点?
    • @lfender6445 好吧,也许您可​​以将所有这些字符串与其他代码放在一个单独的文件中...
    【解决方案8】:

    我的两分钱,逃出行尾以获得缩进:

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

    【讨论】:

      【解决方案9】:

      我来这里是为了寻找一个简单的 1-liner 来删除/更正用于打印的文档字符串的标识级别不会让它看起来不整洁,例如通过制作它在脚本中“挂在函数之外”。

      这是我最终做的:

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

      【讨论】:

      • 这在类方法和嵌套类上失败。
      【解决方案10】:

      对于字符串,您可以在处理字符串之后。对于文档字符串,您需要改为在处理函数之后。这是一个仍然可读的解决方案。

      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__
      

      【讨论】:

      【解决方案11】:

      第一个选项很好 - 包括缩进。 它采用 python 风格 - 为代码提供可读性。

      正确显示:

      print string.lstrip()
      

      【讨论】:

      • 这似乎是格式化三引号字符串的最简单和最干净的方法,因此您不会因为缩进而有多余的空格
      • 这只会删除多行字符串第一行中的前导空格。它对格式化以下行没有帮助。
      【解决方案12】:

      这取决于您希望文本如何显示。如果您希望所有内容都左对齐,则可以将其格式化为第一个 sn-p 或遍历行左修剪所有空间。

      【讨论】:

      • 文档字符串处理工具的工作方式不是删除所有左侧的空格,而是删除与第一缩进行一样多。这种策略更复杂一些,允许您缩进并在后处理的字符串中尊重它。
      【解决方案13】:

      你可以使用这个函数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
      """
      

      【讨论】:

        【解决方案14】:

        使用朱莉娅!在那里它按预期工作......

        julia> s = """Hi,
               new line
               """
        
        
        Hi,\nnew line\n"
        
        julia> println(s)
        Hi,
        new line
        
        
        julia>
        

        【讨论】:

          猜你喜欢
          • 2016-11-25
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-02-05
          • 1970-01-01
          • 1970-01-01
          • 2019-02-04
          相关资源
          最近更新 更多