Python 使用自定义双括号格式的模板安全替换

Python template safe substitution with the custom double-braces format

我正在尝试用 Python 的模板替换 {{var}} 格式的变量。

from string import Template

class CustomTemplate(Template):
    delimiter = '{{'
    pattern = r'''
    \{\{(?:
    (?P<escaped>\{\{)|
    (?P<named>[_a-z][_a-z0-9]*)\}\}|
    (?P<braced>[_a-z][_a-z0-9]*)\}\}|
    (?P<invalid>)
    )
    '''

replacement_dict = {
    "test": "hello"
}

tpl = '''{
    "unaltered": "{{foo}}",
    "replaced": "{{test}}"
}'''

a = CustomTemplate(tpl)
b = a.safe_substitute(replacement_dict)

print(b)

输出:

{
    "unaltered": "{{foo",
    "replaced": "hello"
}

如您所见,{{foo}} 变量(不是替换的一部分)的右括号被砍掉了。我认为这是正则表达式的编写方式(结束\}\})?

我想用模板解决这个问题,而不是用任何其他外部库。

我不确定你是怎么做到的。在 linux 上,在 python 3.4.3 中(我认为我可以使用某些版本的 2.7)我需要将 tpl 设为字符串

tpl = '''
    "unaltered": "{{foo}}",
    "replaced": "{{test}}"
'''

避免出现类型错误

>>> tpl = '''
...     "unaltered": "{{foo}}",
...     "replaced": "{{test}}"
... '''
>>> a = CustomTemplate(tpl)
>>> a.template
'\n    "unaltered": "{{foo}}",\n    "replaced": "{{test}}"\n'
>>> b = a.safe_substitute(replacement_dict)
>>> b
'\n    "unaltered": "{{foo}}",\n    "replaced": "hello"\n'

当我这样做时,{{foo}} 没有改变。

我试过上面的代码,看起来代码实际上不适用于 python 2.7.6。我会看看我是否能找到一种方法让它与 2.7.6 一起工作,因为这似乎是最近 linux 发行版的常见版本。

更新:

看起来这是 2007 年的一个已知错误。http://bugs.python.org/issue1686 Far as I can tell, it was applied to python 3.2 in 2010, and python 2.7 in 2014. As far as getting this to work, you can either apply the patch for issue 1686, or you can override safe_substitute() in your class with the actual source code from this patch https://hg.python.org/cpython/file/8a98ee6baa1e/Lib/string.py

此代码适用于 2.7.6 和 3.4.3

from string import Template
class CustomTemplate(Template):
    delimiter = '{{'
    pattern = r'''
    \{\{(?:
    (?P<escaped>\{\{)|
    (?P<named>[_a-z][_a-z0-9]*)\}\}|
    (?P<braced>[_a-z][_a-z0-9]*)\}\}|
    (?P<invalid>)
    )
    '''

    def safe_substitute(self, *args, **kws):
        if len(args) > 1:
            raise TypeError('Too many positional arguments')
        if not args:
            mapping = kws
        elif kws:
            mapping = _multimap(kws, args[0])
        else:
            mapping = args[0]
        # Helper function for .sub()
        def convert(mo):
            named = mo.group('named') or mo.group('braced')
            if named is not None:
                try:
                    # We use this idiom instead of str() because the latter
                    # will fail if val is a Unicode containing non-ASCII
                    return '%s' % (mapping[named],)
                except KeyError:
                    return mo.group()
            if mo.group('escaped') is not None:
                return self.delimiter
            if mo.group('invalid') is not None:
                return mo.group()
            raise ValueError('Unrecognized named group in pattern',
                             self.pattern)
        return self.pattern.sub(convert, self.template)

replacement_dict = {
    "test": "hello"
}

tpl = '''{
    "escaped": "{{{{",
    "unaltered": "{{foo}}",
    "replaced": "{{test}}",
    "invalid": "{{az"
}'''

a = CustomTemplate(tpl)
b = a.safe_substitute(replacement_dict)

print (b)

结果:

Python 2.7.6 (default, Jun 22 2015, 17:58:13) 
[GCC 4.8.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import template
{
    "escaped": "{{",
    "unaltered": "{{foo}}",
    "replaced": "hello",
    "invalid": "{{az"
}
>>> 

问题

  • 开发人员 reggie 希望将自定义占位符定界符与 python PEP292 模板字符串一起使用。

解决方案(解决方法)

  • developer reggie 可以更改占位符前缀字符。
  • 这种方法允许使用来自完全不同的编程语言的定界符语法。
  • 此方法实现了更改占位符语法的主要目的,以防止分隔符冲突问题。
  • 这种方法提供了最直接的子类化案例 string.Template。

陷阱

  • 此方法不支持像 {{var}}.
  • 这样的双花括号占位符
  • 如果开发人员无法随意更改模板语法,则此方法无济于事。

示例

import string
pass

class TemplateRubyish(string.Template):
  delimiter = '#'
  idpattern = r'[a-z][\w\,\+\=\:\-\.\x2f\x5c\*\(\)\[\]\x7c]*'

class TemplatePerlish(string.Template):
  delimiter = 'qq'
  idpattern = r'[a-z][\w\,\+\=\:\-\.\x2f\x5c\*\(\)\[\]\x7c]*'

replacement_dict = {}
replacement_dict.update({
  "age":      "34",
  "fname":    "Homer",
  "lname":    "Simpson",
});
pass

##
vout     = TemplateRubyish("""\
  Greetings #{fname} #{lname},
  You are #{age}ish years old.
""").safe_substitute(replacement_dict);
print(vout)
pass

##
vout     = TemplatePerlish("""\
  Greetings qq{fname} qq{lname},
  You are qq{age}ish years old.
""").safe_substitute(replacement_dict);
print(vout)
pass

结果

    Greetings Homer Simpson,
    You are 34ish years old.

    Greetings Homer Simpson,
    You are 34ish years old.