我有一个Python编辑器,用户输入脚本或代码,然后将其放入主方法中,同时每行都缩进。问题在于,如果用户有一个多行字符串,整个脚本的缩进会影响该字符串,每个空格都会插入一个制表符。一个有问题的脚本可能如下所示:
"""foo
bar
foo2"""
因此,在主方法中,它看起来应该像:
def main():
"""foo
bar
foo2"""
现在这个字符串的每一行开头都会多了一个制表符。
我有一个Python编辑器,用户输入脚本或代码,然后将其放入主方法中,同时每行都缩进。问题在于,如果用户有一个多行字符串,整个脚本的缩进会影响该字符串,每个空格都会插入一个制表符。一个有问题的脚本可能如下所示:
"""foo
bar
foo2"""
因此,在主方法中,它看起来应该像:
def main():
"""foo
bar
foo2"""
现在这个字符串的每一行开头都会多了一个制表符。
标准库中的 textwrap.dedent 可以自动去除奇怪的缩进。
"""foo
开始,则第一行缺少其他行具有的前导缩进,因此 dedent
不会起作用。如果您等到下一行开始使用 "foo "并像这样转义第一个换行符:"""\\
,那么它就可以正常工作了。 - Scott H根据我的看法,这里更好的答案可能是inspect.cleandoc
,它可以执行textwrap.dedent
的大部分功能,但还可以解决textwrap.dedent
在首行存在问题的情况。
下面的示例显示了区别:
>>> import textwrap
>>> import inspect
>>> x = """foo bar
baz
foobar
foobaz
"""
>>> inspect.cleandoc(x)
'foo bar\nbaz\nfoobar\nfoobaz'
>>> textwrap.dedent(x)
'foo bar\n baz\n foobar\n foobaz\n'
>>> y = """
... foo
... bar
... """
>>> inspect.cleandoc(y)
'foo\nbar'
>>> textwrap.dedent(y)
'\nfoo\nbar\n'
>>> z = """\tfoo
bar\tbaz
"""
>>> inspect.cleandoc(z)
'foo\nbar baz'
>>> textwrap.dedent(z)
'\tfoo\nbar\tbaz\n'
请注意,inspect.cleandoc
还将内部制表符(expands internal tabs)扩展为空格。这可能不适合某些使用情况,但对我来说很好用。cleandoc
函数所做的处理不仅仅是删除缩进。至少需要将制表符('\t'
)扩展为四个空格(' '
)。 - Briantextwrap.dedent(s).strip()
来避免更改制表符并仍然处理前导和尾随换行符。 - DocOctextwrap.dedent
的输出。当我回答这个问题时,我忽略了原始问题的细微差别。然而,我相信我的答案在更广泛的情况下会更有帮助。 - bbenne10\n
时应该小心。 inspect.cleandoc
不会清除它。(有经验的人会知道) - eddym多行字符串的第一行之后的内容属于该字符串的一部分,解释器不会将其作为缩进处理。你可以自由编写:
def main():
"""foo
bar
foo2"""
pass
另一方面,那段代码不易读,并且Python也知道这点。因此,如果文档字符串在它的第二行包含空格,则使用help()
查看文档字符串时会去掉这些空格。 因此,help(main)
和下面的help(main2)
产生相同的帮助信息。
def main2():
"""foo
bar
foo2"""
pass
help()
表现得更好而设计的使用案例。为了在其他地方使用相同的去除缩进逻辑,您可以使用textwrap.dedent()
,并且这个方法已经在基本上每一个答案中被介绍过了。 - SingleNegationElimination我希望保留三引号行之间的内容,只去掉公共的前导缩进。我发现 texwrap.dedent
和 inspect.cleandoc
并不能完全做到这一点,所以我写了这个函数。它使用了 os.path.commonprefix
。
import re
from os.path import commonprefix
def ql(s, eol=True):
lines = s.splitlines()
l0 = None
if lines:
l0 = lines.pop(0) or None
common = commonprefix(lines)
indent = re.match(r'\s*', common)[0]
n = len(indent)
lines2 = [l[n:] for l in lines]
if not eol and lines2 and not lines2[-1]:
lines2.pop()
if l0 is not None:
lines2.insert(0, l0)
s2 = "\n".join(lines2)
return s2
print(ql("""
Hello
|\---/|
| o_o |
\_^_/
"""))
print(ql("""
World
|\---/|
| o_o |
\_^_/
"""))
"""
缩进比引用文本少: Hello
|\---/|
| o_o |
\_^_/
World
|\---/|
| o_o |
\_^_/
我本以为这会更简单些,否则我就不会费心去做了!
textwrap.dedent
和inspect.cleandoc
之间差异的更加清晰的方法:
import textwrap
import inspect
string1="""String
with
no indentation
"""
string2="""String
with
indentation
"""
print('string1 plain=' + repr(string1))
print('string1 inspect.cleandoc=' + repr(inspect.cleandoc(string1)))
print('string1 texwrap.dedent=' + repr(textwrap.dedent(string1)))
print('string2 plain=' + repr(string2))
print('string2 inspect.cleandoc=' + repr(inspect.cleandoc(string2)))
print('string2 texwrap.dedent=' + repr(textwrap.dedent(string2)))
输出
string1 plain='String\nwith\nno indentation\n '
string1 inspect.cleandoc='String\nwith\nno indentation\n '
string1 texwrap.dedent='String\nwith\nno indentation\n'
string2 plain='String\n with\n indentation\n '
string2 inspect.cleandoc='String\nwith\nindentation'
string2 texwrap.dedent='String\n with\n indentation\n'
string1="""
String
with
no indentation
"""
string2="""
String
with
indentation
"""
print('string1 plain=' + repr(string1))
print('string1 inspect.cleandoc=' + repr(inspect.cleandoc(string1)))
print('string1 texwrap.dedent=' + repr(textwrap.dedent(string1)))
print('string2 plain=' + repr(string2))
print('string2 inspect.cleandoc=' + repr(inspect.cleandoc(string2)))
print('string2 texwrap.dedent=' + repr(textwrap.dedent(string2)))
输出
string1 plain='\nString\nwith\nno indentation\n '
string1 inspect.cleandoc='String\nwith\nno indentation\n '
string1 texwrap.dedent='\nString\nwith\nno indentation\n'
string2 plain='\n String\n with\n indentation\n '
string2 inspect.cleandoc='String\nwith\nindentation'
string2 texwrap.dedent='\nString\nwith\nindentation\n'
如果我正确理解问题,那么这就是一个解决方法。lstrip()会移除前导空格,因此它将移除制表符和空格。
from os import linesep
def dedent(message):
return linesep.join(line.lstrip() for line in message.splitlines())
例子:
name='host'
config_file='/Users/nmellor/code/cold_fusion/end-to-end/config/stage.toml'
message = f"""Missing env var or configuration entry for 'host'.
Please add '{name}' entry to file
{config_file}
or export environment variable 'mqtt_{name}' before
running the program.
"""
>>> print(message)
Missing env var or configuration entry for 'host'.
Please add 'host' entry to
'/Users/nmellor/code/cold_fusion/end-to-end/config/stage.toml'
or export environment variable 'mqtt_host' before
running the program.
>>> print(dedent(message))
Missing env var or configuration entry for 'host'.
Please add 'host' entry to file
'/Users/nmellor/code/cold_fusion/end-to-end/config/stage.toml'
or export environment variable 'mqtt_host' before
running the program.
.dedent()
将不起作用。我唯一看到的方法是为每行从第二个字符开始,剥离前n个tab,其中n是主方法的已知缩进。
如果事先不知道该缩进-您可以在插入之前添加尾随换行符,并从最后一行剥离制表符数量...
第三种解决方案是解析数据并查找多行引用的开头,在它关闭之前,不要在每行后面添加您的缩进。
认为有更好的解决方案..
re
来解决这个问题: print(re.sub('\n *','\n', f"""Content-Type: multipart/mixed; boundary="===============9004758485092194316=="
` MIME-Version: 1.0
Subject: Get the reader's attention here!
To: recipient@email.com
--===============9004758485092194316==
Content-Type: text/html; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Very important message goes here - you can even use <b>HTML</b>.
--===============9004758485092194316==--
"""))
在上面的代码中,我保持了代码的缩进,但是字符串基本上被修剪了。每行开头的所有空格都被删除了。这很重要,因为在SMTP或MIME特定行前面的任何空格都会破坏电子邮件消息。
我做出的权衡是将Content-Type
留在第一行,因为我使用的regex
没有删除初始的\n
(这会破坏电子邮件)。如果这让我很困扰,我想我可以像这样添加一个lstrip:
print(re.sub('\n *','\n', f"""
Content-Type: ...
""").lstrip()
re.sub
,因为我并没有真正理解textwrap
和inspect
的所有细微差别。有一个更简单的方法:
foo = """first line\
\nsecond line"""
如果我理解正确的话,您会正确缩进并将用户输入与程序余下部分整合在一起(然后运行整个程序)。
因此,在将用户输入放入程序后,您可以运行正则表达式,基本上将强制缩进恢复。类似于:在三引号内,用一个“新行标记”替换所有跟随四个空格(或制表符)的“新行标记”。