Python中使用re.MULTLINE匹配一个字符串的多行

有时会遇到这种类型的需求:在一个包括换行符的字符串中,匹配行首以某字符(比如+)开头的行数。

当然,这只是一个具体化的问题,但背后有一个通用场景:如何把一个字符串当做多行来处理,并且还比较高效。

一个形如a\nb\n+c\n+d的字符串,要想知道+开头的行数,大概有以下几种做法。

先按行切分为列表

最差版本

>>> s = 'a\nb\n+c\n+d'
>>> lines = s.split('\n')
>>> len([x for x in lines if x.startswith('+')])
2

以上是最直观的版本,也是最差的版本。 它有许多可以优化的细节。

优化分行

lines = s.splitlines()

直接通过\n来分行,容易出问题。 换行符一共有三种:

  1. \n:Unix及其衍生系统,如Linux、FreeBSD、Mac OS X。
  2. \r\n:Dos衍生系统,主要是Windows。
  3. \r:早期Mac OS,以及一些特殊场景。

其中,\r是回车符号,即return,含义是输入位置移动到行首,对应的是早期打字机把滚筒向左移动到行首的操作; \n是换行符,即newline,含义是输入位置移动到下一行。 由此可见,\r\n才是一次完整的换行操作。 而在Unix系统中,省略\r可以减少编码,因为newline的输入位置一定是行首,丢掉了历史包袱,现在更为流行。

但无论如何,鉴于换行符的复杂性,分行操作建议用splitlines来实现。

改len为sum

sum(1 for x in lines if x.startswith('+'))

由于这里只需要个数,而不需要内容,因此可以用sum来实现计算,省略了生成列表的开销。 对迭代器计算次数,这种方法可以通用。

利用re.MULTLINE做多行匹配

上面的方案有个问题,就是一定要把一个字符串先分为多个小字符串列表。 这在超大字符串场景下,会出现显著的性能问题,对内存的无效占用很高。

利用正则表达式的多行匹配可以解决这个问题。

>>> s = 'a\nb\n+c\n+d'
>>> import re
>>> sum(1 for i in re.finditer(r'^\+', s, re.MULTILINE))
2

这里用到了标准库的re模块。 由于re.search只能找一个,re.find返回的是一个列表,因此这里选择了re.finditer,空间占用降低到了极致。


相关笔记