Python是否有用于取消缩进多行字符串的内置函数?

67

假设我有一个字符串

s = """
    Controller = require 'controller'

    class foo
        view: 'baz'
        class: 'bar'

        constructor: ->
            Controller.mix @
"""

现在字符串中的每一行都有一个全局4个空格的缩进。如果该字符串在函数内部声明,则会有一个8个空格的全局缩进等等。

Python是否有用于删除字符串全局左缩进的函数?

我希望该函数的输出为:

Controller = require 'controller'

class foo
    view: 'baz'
    class: 'bar'

    constructor: ->
        Controller.mix @"
4个回答

115

不是内置函数,但是标准库中有一个名为textwrap.dedent()的函数。

>>> print(textwrap.dedent(s))

Controller = require 'controller'

class foo
    view: 'baz'
    class: 'bar'

    constructor: ->
        Controller.mix @

3
我不知道它的存在。有用的东西。+1。 - Gareth Latty
1
这里是dedent的源代码:http://hg.python.org/cpython/file/2.7/Lib/textwrap.py - Jiri
1
textwrap.dedent(s) 的输出以换行符开头,但这并不是 OP 所要求的。 - Anthon
8
如果你在s="""\中转义第一个换行符,那么使用dedent将不会生成第一个空行。 - firegurafiku
2
当然不会,因为它不存在。真正的问题是当它存在时它没有被移除,这就是 OP 所要求的。 - Anthon
显示剩余2条评论

25

我知道这个问题已经有答案了,但还有另一种方法:

import inspect

def test():
    t = """
    some text
    """

    return inspect.cleandoc(t)

print(test())

4
对于某些情况来说,这比textwrap.dedent更好,因为它还会去除开头和结尾的换行符。 - rjh
1
一直在寻找这个+1。这是textwrap.dedent()的有用替代品。只有当缩进等于或小于全局缩进时,才会删除尾随换行符。 - M. Schlenker
这比 textwrap.dedent 好,谢谢! - rsomething

10

textwrap.dedent()接近你想要的,但它没有实现你要求的内容,因为它有一个前导换行符。你可以将dedent包装在一个函数中,从s中删除前导换行符:

def my_dedent(string):
    if string and string[0] == '\n':
        string = string[1:]
    return textwrap.dedent(string)

然而,textwrap.dedent()以一种特殊的方式处理只有空格的行,如果您正在从缩进的多行语句生成Python源代码,则可以使用此方式,其中尾随空格是不重要的。

但是,通常情况下,textwrap.dedent()删除具有比“最大缩进”更多空格的行上的额外空格,从所有空白行中删除空格,并且在关闭"""之前丢弃任何空格,特别是因为此行为未记录并使用非透明正则表达式执行

由于我还会生成非Python源代码,其中空格通常很重要,因此我使用以下例程。 它不能处理TAB缩进,但它确实为您提供了您所请求的输出,而textwrap.dedent()失败。

def remove_leading_spaces(s, strict=False):
    '''Remove the maximum common spaces from all non-empty lines in string

Typically used to remove leading spaces from all non-empty lines in a
multiline string, preserving all extra spaces.
A leading newline (when not useing '"""\') is removed unless the strict
argument is True.

Note that if you want two spaces on the last line of the return value 
without a newline, you have to use the max indentation + 2 spaces before 
the closing """. If you just input 2 spaces that is likely to be the 
maximum indent.
    '''
    if s and not strict and s[0] == '\n':
        s = s[1:]
    lines = s.splitlines(True) # keep ends
    max_spaces = -1
    for line in lines:
        if line != '\n':
            for idx, c in enumerate(line[:max_spaces]):
                if not c == ' ':
                    break
            max_spaces = idx + 1
    return ''.join([l if l == '\n' else l[max_spaces-1:] for l in lines])

1
OP 没有询问关于前导换行符或删除相同的内容。最简单的解决方法是在前导三引号后面加上反斜杠来消除前导换行符(正如 @firegurafiku 已经指出的那样)。也许有理由用自定义函数替换提供的 textwrap.dedent(),但删除换行符,只需使用反斜杠避免即可,这似乎不是一个好的理由。 - gwideman
1
程序化地删除初始换行符是必要的,以从OP提供的输入获得所请求的输出。总有不同的处理方式,例如使用显式反斜杠在一行上重写字符串。但是,正如您所暗示的那样,OP并没有询问如何修改他的输入。 - Anthon
1
似乎OP的任务不是一般地处理字符串,而是如何编写一个多行字符串文字,使其源缩进与周围源缩进相匹配,但结果字符串不包含该缩进。 OP的具体问题是“Python有没有用于删除字符串全局左缩进的函数?”前导换行符问题很重要,但不需要由所请求的函数删除。 话虽如此,您关于dedent在某些情况下删除额外空格的说明有点令人担忧,需要了解更多相关信息。 - gwideman

-1

我可以通过回车键来实现这个:

s = """
    \r Controller = require 'controller'
    \r
    \rclass foo
    \r    view: 'baz'
    \r    class: 'bar'
    \r
    \r    constructor: ->
    \r        Controller.mix @
    \r"""

这是一个很好的解决方案,可以去除前导空格。对我有用。不明白为什么有人会给负面评价。 - Shh
绝对是一个有创意的解决方案! - Timothy C. Quinn
1
这并不会删除任何东西。它只是让你在打印到终端时,看起来像有东西被删除了! - Kuba hasn't forgotten Monica
正如@Kubahasn'tforgottenMonica所指出的那样,看起来只是空格被删除了。例如,将字符串写入文件将包括隐藏空格的字节。这在紧急情况下可以使用,而且没有第三方依赖,但这不是一个适合重复使用或用于除了打印文本到控制台之外的任何其他情况的好解决方案。 - Erik Goepfert

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