我使用的是python 3.6到3.8。
我正在尝试用从文件中读取的文本中的单个空格替换单个换行符的任何实例。 我的目标是将段落压缩成单行文本,以便按textwrap
重新包装。 由于textwrap
只适用于单个段落,因此我需要一种简单的方法来检测/描绘段落,并且将它们压缩成一行文本似乎是最方便的。 为了使其正常工作,顺序中两个或多个换行符的任何实例都会定义段落边界,并且应保留
。 我的第一个尝试是使用前瞻/后视断言,坚持我替换的任何换行符不受其他换行符的限制:
re.sub(r'(?<!n)n(?!n)', ' ', input_text)
在大多数情况下,这工作正常。 但是,我很快就遇到了一个情况,有人有一个包含其他空格的段落分隔符。
这是一些以一小段开头的示例文本。\这第二段足够长,可以跨行拆分,因此它中间包含a 单个换行符。 这第三段前面有一个不寻常的分隔符;一个换行符后跟a 空格,后跟另一个换行符。 这是一个需要处理的特殊情况。
我的前瞻/后视断言策略在这里不起作用,因为所需的后视需要具有不确定的长度(也许空间在那里,也许没有(,这是不允许的。
# this is an error
re.sub(r'(?<!ns*)n(?!s*n)', ' ', input_text)
我的下一次尝试是分两次执行此操作,删除换行符之间的任何非换行空格,但我找不到可以完美做到这一点的正则表达式。 这有点有效,但会压缩任何超过两个换行符的出现。
# this compresses "nnn" or "nn n" into "nn"
re.sub(r'(?<!n)n(?!n)', ' ', re.sub(r'ns*n', 'nn', input_text))
我想避免这种情况,因为段落之间多余的空白行可能是故意的;它们应该单独保留。
s
的 unicode 定义不够具体,无法让我构造"除换行符外的所有空格"的字符集,所以我不能做这样的事情:
# this only works for ASCII
re.sub(r'(?<!n)n(?!n)', ' ', re.sub(r'n[ trfv]*n', 'nn', input_text))
为此,我需要一种方法来表达 unicode 的"s
n
除外",我认为这不存在。 我在百灵鸟上尝试了[s!n]
,奇怪的是,它似乎在 3.6.5 和 3.8.0 中做了正确的事情。 尽管!
在任一版本的字符集中都没有记录的效果,并且re.escape()
的文档明确指出,从 3.7 开始,该方法不再转义!
,因为它不是特殊字符。
# this appears to work, but the docs say it shouldn't
re.sub(r'(?<!n)n(?!n)', ' ', re.sub(r'n[s!n]n', 'nn', input_text))
尽管它似乎有效,但出于显而易见的原因,我不想依赖这种行为。 我可能应该将其报告为代码或文档中的错误。
假设最后一个不应该被支持,我错过了什么其他方法?
您可以捕获双换行符和更多换行符的出现,以便在匹配时保留它们,并仅匹配所有其他换行符:
import re
text = "This is some sample text beginning with a short paragraph.nnThis second paragraph is long enough to be split across lines, so it containsna single newline in the middle.n nThis third paragraph has an unusual separator before it; a newline followed byna space followed by another newline. It's a special case that needs to benhandled."
print( re.sub(r'([^Sn]*n(?:[^Sn]*n)+[^Sn]*)|[^Sn]*n[^Sn]*', lambda x: x.group(1) or ' ', text) )
查看 Python 演示
详
([^Sn]*n(?:[^Sn]*n)+[^Sn]*)
- 组 1:0+ 换行符以外的空格,换行符,然后出现- 1 个或多个(因此,至少匹配两个换行符(除换行符和换行符以外的 0+ 空格,然后再次出现 0+ 换行符以外的空格
|
- 或- 空格、换行符和换行符以外的 0+ 空格
[^Sn]*n[^Sn]*
- 0+ 换行符以外的替换lambda x: x.group(1) or ' '
:如果组 1 匹配,则不应进行替换,否则,用空格替换。