如何制作正则表达式贪婪但可选择的'

时间:2017-03-29 09:39:48

标签: python regex

我试图为表示文件路径的字符串编写解析器,可选地后跟冒号(:)和表示访问标记的字符串(例如r+或{{ 1}})。文件名本身可以包含冒号,例如w,因此分隔访问标志的冒号应该是字符串中的最后一个冒号。

到目前为止,这是我的实现:

foo:bar.txt

我已经尝试过以下测试:

import re

def parse(string):
    SCHEME = r"file://"                             # File prefix
    PATH_PATTERN = r"(?P<path>.+)"                  # One or more of any character
    FLAGS_PATTERN = r"(?P<flags>.+)"        # The letters r, w, a, b, a '+' symbol, or any digit

    # FILE_RESOURCE_PATTERN = SCHEME + PATH_PATTERN + r":" + FLAGS_PATTERN + r"$"               # This makes the first test pass, but the second one fail
    FILE_RESOURCE_PATTERN = SCHEME + PATH_PATTERN + optional(r":" + FLAGS_PATTERN) + r"$"   # This makes the second test pass, but the first one fail

    tokens = re.match(FILE_RESOURCE_PATTERN, string).groupdict()

    return tokens['path'], tokens['flags']

def optional(re):
    '''Encloses the given regular expression in a group which matches 0 or 1 repetitions.'''
    return '({})?'.format(re)

问题在于,通过使用或不使用import pytest def test_parse_file_with_colon_in_file_name(): assert parse("file://foo:bar.txt:r+") == ("foo:bar.txt", "r+") def test_parse_file_without_acesss_flags(): assert parse("file://foobar.txt") == ("foobar.txt", None) if __name__ == "__main__": pytest.main([__file__]) ,我可以进行一个或另一个测试通过,但不能同时进行。如果我使optional可选,则前面的正则表达式会占用整个字符串。

如何调整r":" + FLAGS_PATTERN方法以使两个测试都通过?

2 个答案:

答案 0 :(得分:1)

你应该建立像

这样的正则表达式
^file://(?P<path>.+?)(:(?P<flags>[^:]+))?$

请参阅regex demo

在您的代码中,^锚点不是必需的,因为您使用re.match在字符串的开头锚定匹配项。 path组可以懒惰地匹配任何1个字符(因此,所有可以与第2组匹配的文本将在第二次捕获中着陆),直到第一次出现:后跟1+个字符除了:(如果存在),然后测试字符串位置的结尾。感谢$锚点,如果第二个可选组不匹配,第一个组将匹配整个字符串。

使用以下修复程序:

PATH_PATTERN = r"(?P<path>.+?)"                  # One or more of any character
FLAGS_PATTERN = r"(?P<flags>[^:]+)"        # The letters r, w, a, b, a '+' symbol, or any digit

请参阅online Python demo

答案 1 :(得分:1)

为了好玩,我写了这个解析函数,我认为这比使用RE更好?

def parse(string):
    s = string.split('//')[-1]
    try:
        path, flags = s.rsplit(':', 1)
    except ValueError:
        path, flags = s.rsplit(':', 1)[0], None
    return path, flags