【问题标题】:Using a regex as a template with Python在 Python 中使用正则表达式作为模板
【发布时间】:2010-09-08 22:04:51
【问题描述】:

我有使用正则表达式模式作为模板的想法,想知道在 Python(3 或更新版本)中是否有方便的方法。

import re

pattern = re.compile("/something/(?P<id>.*)")
pattern.populate(id=1) # that is what I'm looking for

应该会导致

/something/1

【问题讨论】:

  • 这不是正则表达式的真正用途。 SilentGhost 的示例正是您要找的。​​span>
  • 是的,我知道这不是正则表达式的用途,但我可以使用正则表达式进行匹配并在我的情况下作为模板。
  • 它对于像 Django 在 {% url %} 标签中那样构建 url 很有用。
  • 这将非常有用。如果您可以使用与路由 URL(即正则表达式)相同的代码生成 URL(即字符串格式),那就太好了。
  • 我只是把这样的东西放在一起......见下文。

标签: python regex templates


【解决方案1】:

这不是正则表达式的用途,您可以使用普通的字符串格式。

>>> '/something/{id}'.format(id=1)
'/something/1'

【讨论】:

    【解决方案2】:

    下面是我创建的一个轻量级类,它可以满足您的需求。您可以编写一个正则表达式,并将该表达式用于两个匹配字符串和生成字符串。

    代码底部有一个小例子说明如何使用。

    一般情况下,您可以正常构造正则表达式,并正常使用matchsearch 函数。 format 函数的使用很像 string.format 来生成新字符串。

    import re
    regex_type = type(re.compile(""))
    
    # This is not perfect. It breaks if there is a parenthesis in the regex.
    re_term = re.compile(r"(?<!\\)\(\?P\<(?P<name>[\w_\d]+)\>(?P<regex>[^\)]*)\)")
    
    class BadFormatException(Exception):
        pass
    
    class RegexTemplate(object):
        def __init__(self, r, *args, **kwargs):
            self.r = re.compile(r, *args, **kwargs)
        
        def __repr__(self):
            return "<RegexTemplate '%s'>"%self.r.pattern
        
        def match(self, *args, **kwargs):
            '''The regex match function'''
            return self.r.match(*args, **kwargs)
        
        def search(self, *args, **kwargs):
            '''The regex match function'''
            return self.r.search(*args, **kwargs)
        
        def format(self, **kwargs):
            '''Format this regular expression in a similar way as string.format.
            Only supports true keyword replacement, not group replacement.'''
            pattern = self.r.pattern
            def replace(m):
                name = m.group('name')
                reg = m.group('regex')
                val = kwargs[name]
                if not re.match(reg, val):
                    raise BadFormatException("Template variable '%s' has a value "
                        "of %s, does not match regex %s."%(name, val, reg))
                return val
            
            # The regex sub function does most of the work
            value = re_term.sub(replace, pattern)
            
            # Now we have un-escape the special characters. 
            return re.sub(r"\\([.\(\)\[\]])", r"\1", value)
    
    def compile(*args, **kwargs):
        return RegexTemplate(*args, **kwargs)
        
    if __name__ == '__main__':
        # Construct a typical URL routing regular expression
        r = RegexTemplate(r"http://example\.com/(?P<year>\d\d\d\d)/(?P<title>\w+)")
        print(r)
        
        # This should match
        print(r.match("http://example.com/2015/article"))
        # Generate the same URL using url formatting.
        print(r.format(year = "2015", title = "article"))
        
        # This should not match
        print(r.match("http://example.com/abcd/article"))
        # This will raise an exception because year is not formatted properly
        try:
            print(r.format(year = "15", title = "article"))
        except BadFormatException as e:
            print(e)
        
    

    有一些限制:

    • 格式函数仅适用于关键字参数(您不能使用\1 样式格式,如string.format)。
    • 还有一个将元素与子元素匹配的错误,例如,RegexTemplate(r'(?P&lt;foo&gt;biz(baz)?)')。这可以通过一些工作来纠正。
    • 如果您的正则表达式包含命名组之外的字符类(例如,[a-z123]),我们将不知道如何格式化。

    【讨论】:

      【解决方案3】:

      在替换之后保存编译:

      pattern = re.compile("/something/(?P<%s>.*)" % 1)
      

      【讨论】:

        【解决方案4】:

        对于非常简单的情况,最简单的方法可能是将命名的捕获组替换为格式字段。

        这是一个基本的验证器/格式化器:

        import re
        from functools import partial
        
        unescape = partial(re.compile(r'\\(.)').sub, r'\1')
        namedgroup = partial(re.compile(r'\(\?P<(\w+)>.*?\)').sub, r'{\1}')
        
        
        class Mould:
            def __init__(self, pattern):
                self.pattern = re.compile(pattern)
                self.template = unescape(namedgroup(pattern))
        
            def format(self, **values):
                try:
                    return self.template.format(**values)
                except KeyError as e:
                    raise TypeError(f'Missing argument: {e}') from None
        
            def search(self, string):
                try:
                    return self.pattern.search(string).groupdict()
                except AttributeError:
                    raise ValueError(string) from None
        

        因此,例如,以(XXX) YYY-ZZZZ 的形式为电话号码实例化验证器/格式化程序:

        template = r'\((?P<area>\d{3})\)\ (?P<prefix>\d{3})\-(?P<line>\d{4})'
        phonenum = Mould(template)
        

        然后:

        >>> phonenum.search('(333) 444-5678')
        {'area': '333', 'prefix': '444', 'line': '5678'}
        
        >>> phonenum.format(area=111, prefix=555, line=444)
        (111) 555-444
        

        但这是一个非常基本的骨架,它忽略了许多正则表达式功能(例如环视或非捕获组)。如果需要它们,事情很快就会变得非常混乱。在这种情况下,反过来:从模板生成模式虽然更冗长,但可能更灵活且不易出错。

        这里是基本的验证器/格式化器(.search().format() 相同):

        import string
        import re
        
        FMT = string.Formatter()
        
        
        class Mould:
            def __init__(self, template, **kwargs):
                self.template = template
                self.pattern = self.make_pattern(template, **kwargs)
        
            @staticmethod
            def make_pattern(template, **kwargs):
                pattern = ''
                # for each field in the template, add to the pattern
                for text, field, *_ in FMT.parse(template):
                    # the escaped preceding text
                    pattern += re.escape(text)
                    if field:
                        # a named regex capture group
                        pattern += f'(?P<{field}>{kwargs[field]})'
                    # XXX: if there's text after the last field,
                    #   the parser will iterate one more time,
                    #   hence the 'if field'
                return re.compile(pattern)
        

        实例化:

        template = '({area}) {prefix}-{line}'
        content  = dict(area=r'\d{3}', prefix=r'\d{3}', line=r'\d{4}')
        phonenum = Mould(template, **content)
        

        执行:

        >>> phonenum.search('(333) 444-5678')
        {'area': '333', 'prefix': '444', 'line': '5678'}
        
        >>> phonenum.format(area=111, prefix=555, line=444)
        (111) 555-444
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2010-10-16
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-06-27
          相关资源
          最近更新 更多