当此字符串中的行末尾有数字时,为什么此正则表达式不排除该行

时间:2012-12-14 17:10:30

标签: python regex

我正在尝试扫描文档并确定文档各部分的开始和结束位置。有时,该文档有一个目录,列出了我不想捕获TOC的页码,因为它不能识别文档的一部分。我有一段时间一直搞乱这个问题而且我一直坚持下去。我似乎无法避免使用行号

从内容列表中捕获行

这是正则表达式

verbose_item_pattern_3 = re.compile(r"""
  ^            # begin match at newline
  \t*          # 0-or-more tabspace
  [ ]*         # 0-or-more blank space
  I            # a capital I
  [tT][eE][mM] # one character from each of the three sets this allows for unknown case
  \t*          # 0-or-more tabspace
  [ ]*         # 0-or-more blankspace
  \d{1,2}      # 1-or-2 digits
  [.]?         # 0-or-1 literal .
  \(?          # 0-or-1 literal open paren
  [a-e]?       # 0-or-1 letter in the range a-e
  \)?          # 0-or-1 closing paren
  .*           # any number of unknown characters so we can have words and punctuation
  [^0-9]       # anything but [0-9]
  $           # 1 newline character
  """, re.VERBOSE|re.MULTILINE)

这是一个我不想捕获的行的例子

test_string='\nItem 6.       TITLE ITEM 6..................................................25\n'

以下是我想要捕获的示例

test_string='\nItem 6.       TITLE ITEM 6 maybe other words here who knows  \n'

但是当我跑步时

re.findall(verbose_item_pattern_3,test_string)

结果是

['Item 6.       TITLE ITEM 6..................................................25\n']

现在我觉得有趣的是,如果我的测试字符串是这个

test_string='PART I\nItem 1.       TITLE ITEM 1...................................................1\nItem 2.       TITLE ITEM 2..................................................21\n'

然后运行它     re.findall(verbose_item_pattern_3,test_string)

结果更接近我想要的但仍然不正确

['Item 2.       TITLE ITEM 2..................................................21\n']

不应该捕获任何东西

2 个答案:

答案 0 :(得分:2)

你的正则表达式匹配因为三件事。

  1. 大部分是可选的,所以非常不明确
  2. 有一个.*吃了整条线,所以你的最后一个条件[^0-9]将永远无法承受,那是因为:
  3. 换行符本身履行 [^0-9],因此即使该行以数字结尾,[^0-9]也能成功匹配。
  4. 最小的改变是在最后使用负面的后视:

    verbose_item_pattern_3 = re.compile(r"""
      ^            # start-of-line
      \t*          # 0-or-more tabspace
      [ ]*         # 0-or-more blank space
      I            # a capital I
      [tT][eE][mM] # one character from each of the three sets this allows for unknown case
      \t*          # 0-or-more tabspace
      [ ]*         # 0-or-more blankspace
      \d{1,2}      # 1-or-2 digits
      [.]?         # 0-or-1 literal .
      \(?          # 0-or-1 literal open paren
      [a-e]?       # 0-or-1 letter in the range a-e
      \)?          # 0-or-1 closing paren
      .*           # any number of unknown characters so we can have words and punctuation
      $            # end-of-line
      (?<![0-9])   # NOT preceded by a decimal digit (via look-behind)
      """, re.VERBOSE|re.MULTILINE)
    

    请注意,^而不是$实际上都不匹配换行符。它们在(^)或之前的位置$)后面的位置匹配位置。换行符本身永远不会成为比赛的一部分。

    由于这个原因,我已将他们的评论更改为更精确的start-of-lineend-of-line

    另请注意,即使在$之后,我仍然可以应用负面观察。这样做有助于防止回溯,使正则表达式更快。<​​/ p>

答案 1 :(得分:2)

如果我理解正确,您希望示例字符串不匹配,因为该行中的最后一个字符是数字,而您的正则表达式以[^0-9]$结尾。

行为不正确的原因是$将在\n之前匹配,但也会在字符串的最后匹配。最后发生的事情是.*与数字匹配,然后[^0-9]匹配\n,并在字符串末尾匹配$。请考虑以下示例,该示例使用捕获组来说明其工作原理:

>>> re.match(r'(.*)([^0-9])$', '...12\n').groups()
('...12', '\n')

要解决此问题,您可以通过将换行符更改为[^0-9]来阻止[^0-9\n]匹配换行符:

verbose_item_pattern_3 = re.compile(r"""
  ^            # begin match at newline
  \t*          # 0-or-more tabspace
  [ ]*         # 0-or-more blank space
  I            # a capital I
  [tT][eE][mM] # one character from each of the three sets this allows for unknown case
  \t*          # 0-or-more tabspace
  [ ]*         # 0-or-more blankspace
  \d{1,2}      # 1-or-2 digits
  [.]?         # 0-or-1 literal .
  \(?          # 0-or-1 literal open paren
  [a-e]?       # 0-or-1 letter in the range a-e
  \)?          # 0-or-1 closing paren
  .*           # any number of unknown characters so we can have words and punctuation
  [^0-9\n]     # anything but [0-9] and line breaks
  $           # 1 newline character
  """, re.VERBOSE|re.MULTILINE)

示例(使用上面的正则表达式):

>>> verbose_item_pattern_3.findall('\nItem 6.       TITLE ITEM 6.....25\n')
[]
>>> verbose_item_pattern_3.findall('\nItem 6.       TITLE ITEM 6.....\n')
['Item 6.       TITLE ITEM 6.....']