【问题标题】:In Python, how to make a regular expression not ignore the next regular expression if the latter is 'optional'在 Python 中,如果后者是“可选”的,如何使正则表达式不忽略下一个正则表达式
【发布时间】:2017-02-23 15:44:15
【问题描述】:

我正在尝试为诸如

之类的表达式编写解析器

“每周从 2017-11-03 15:00:00 到 2017-11-03 16:00:00 到 2017-12-03”

代表一个循环的时间间隔。最终,我希望能够使用已解析的字段初始化 dateutil.rrule 对象。大多数rrule 参数是可选的,但是,它们在字符串表示中对应于可能存在或不存在的模式。

但是,我无法防止前面的模式“过于贪婪”。考虑以下带有两个测试用例的示例:

import re
import pytest

from dateutil.rrule import FREQNAMES

def match_pattern(string):
    SPACES = r'\s*'

    freq_names = [freq.lower() for freq in FREQNAMES] + [freq.title() for freq in FREQNAMES]
    FREQ_PATTERN = '(?P<freq>{})?'.format("|".join(freq_names))

    START_PATTERN = 'from' + SPACES + r'(?P<start>.+)'
    END_PATTERN = 'till' + SPACES + r'(?P<end>.+)'

    UNTIL_PATTERN = optional('until' + SPACES + r'(?P<until>.+)')
    # UNTIL_PATTERN = 'until' + SPACES + r'(?P<until>.+)'

    PATTERN = SPACES + FREQ_PATTERN \
            + SPACES + START_PATTERN \
            + SPACES + END_PATTERN \
            + SPACES + UNTIL_PATTERN + SPACES

    return re.match(PATTERN, string).groupdict()

def optional(pattern):
    '''Encloses the given regular expression in an optional group (i.e., one that matches 0 or 1 repetitions of the original regular expression).'''
    return '({})?'.format(pattern)

'''Tests'''
def test_match_pattern():
    string = "Weekly from 2017-11-03 15:00:00 till 2017-11-03 16:00:00"

    groups = match_pattern(string)
    assert groups['freq'] == "Weekly"
    assert groups['start'].strip() == "2017-11-03 15:00:00"
    assert groups['end'].strip() == "2017-11-03 16:00:00"

def test_match_pattern_with_until():
    string = "Weekly from 2017-11-03 15:00:00 till 2017-11-03 16:00:00 until 2017-12-03"

    groups = match_pattern(string)
    assert groups['freq'] == "Weekly"
    assert groups['start'].strip() == "2017-11-03 15:00:00"
    assert groups['end'].strip() == "2017-11-03 16:00:00"
    assert groups['until'].strip() == "2017-12-03"

if __name__ == "__main__":
    # pytest.main([__file__])
    pytest.main([__file__+"::test_match_pattern", "-s"])
    # pytest.main([__file__+"::test_match_pattern_with_until", "-s"])

在这里,我想在字符串中设置UNTIL_PATTERN 可选;因此,我使用optional 函数将其包含在()? 中。然而,问题在于这会导致第二次测试失败:

>       assert groups['end'].strip() == "2017-11-03 16:00:00"
E       assert '2017-11-03 1...il 2017-12-03' == '2017-11-03 16:00:00'
E         - 2017-11-03 16:00:00 until 2017-12-03
E         + 2017-11-03 16:00:00

parse_date.py:44: AssertionError
=========================== 1 failed in 0.07 seconds ===========================

问题是,当我将UNTIL_PATTERN 设为可选时,END_PATTERN 过于贪婪并一直消耗到字符串的末尾。 (如果我不设置 optional(),则第二个测试通过但第一个测试不匹配)。

如何让两个测试都通过?

【问题讨论】:

  • 日期,如2017-11-03 15:00:00,似乎有一个非常标准的格式。尝试使用该信息来帮助解析。
  • 您实际上是以错误的顺序执行此操作。在将其分解为多个部分之前,必须建立/修改/测试整体模式。这意味着必须首先测试每个意外事件。这可能意味着您必须丢失点元字符并替换为更窄的子表达式。

标签: python regex


【解决方案1】:

您只需进行两个小改动。首先,让END_PATTERN 不贪心:

(?P<end>.+?)

但是现在,因为它会尽可能少地匹配,所以你必须强制它匹配到字符串的末尾,并使用字符串结尾锚$

PATTERN = SPACES + FREQ_PATTERN \
        + SPACES + START_PATTERN \
        + SPACES + END_PATTERN \
        + SPACES + UNTIL_PATTERN + SPACES + '$'

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-02
    • 2023-01-31
    • 2012-11-30
    • 1970-01-01
    • 2011-07-26
    相关资源
    最近更新 更多