【问题标题】:How to escape $ on Python string Template class?如何在 Python 字符串模板类上转义 $?
【发布时间】:2012-09-27 21:35:54
【问题描述】:

简介

字符串模块有一个模板类,它允许您使用映射对象在字符串中进行替换,例如:

>>> string.Template('var is $var').substitute({'var': 1})
'var is 1'

如果尝试替换映射中缺少的元素,则替换方法可能会引发 KeyError 异常,例如

>>> string.Template('var is $var and foo is $foo').substitute({'var': 1})
KeyError: 'foo'

如果模板字符串无效,则可能会引发 ValueError,例如它包含一个$ 字符,后跟一个空格:

>>> string.Template('$ var is $var').substitute({'var': 1})
ValueError: Invalid placeholder in string: line 1, col 1

问题

给定一个模板字符串和一个映射,我想确定模板中的所有占位符是否都将被替换。为此,我会尝试进行替换并捕获任何 KeyError 异常:

def check_substitution(template, mapping):
    try:
        string.Template(template).substitute(mapping)
    except KeyError:
        return False
    except ValueError:
        pass
    return True

但这不起作用,因为如果模板无效并且引发了 ValueError,则不会捕获后续的 KeyErrors:

>>> check_substitution('var is $var and foo is $foo', {'var': 1})
False
>>> check_substitution('$ var is $var and foo is $foo', {'var': 1})
True

但我不关心 ValueErrors。那么,解决这个问题的正确方法是什么?

【问题讨论】:

  • 模板有一个 safe_substitute 方法,它忽略任何 ValueError 并继续进行替换。问题是它也忽略了 KeyErrors。
  • 可能的ValueError 案例都与非法使用$ 字符有关,对吧?那么为什么不对字符串做一些预处理来避免非法使用$呢?
  • @PedroRomano 这可行,但需要找出所有触发ValueError 的情况。

标签: python string try-catch


【解决方案1】:

Python 不会对多行进行字符串替换

如果你有这个字符串

criterion = """
    <criteria>
    <order>{order}</order>
      <body><![CDATA[{code}]]></body>
    </criteria>
"""

criterion.format(dict(order="1",code="Hello")

结果:

KeyError: 'order'

一种解决方案是使用 string.Template 模块

from string import Template

criterion = """
    <criteria>
    <order>$order</order>
      <body><![CDATA[$code]]></body>
    </criteria>
"""

Template(criterion).substitute(dict(order="1",code="hello")

注意:您必须在关键字前面加上 $,而不是将它们包装在 {}

输出是:

 <criteria>
    <order>1</order>
      <body><![CDATA[hello]]></body>
    </criteria>

完整文档为:https://docs.python.org/2/library/string.html#template-strings

【讨论】:

  • “Python 不会在多行上进行字符串替换” - 错误。使用criterion.format(**dict(order="1", code="Hello") 或仅使用criterion.format(order="1", code="Hello")
【解决方案2】:

The docs say 你可以替换模式,只要它包含所有必要的命名组:

import re
from string import Template


class TemplateIgnoreInvalid(Template):
    # override pattern to make sure `invalid` never matches
    pattern = r"""
    %(delim)s(?:
      (?P<escaped>%(delim)s) |   # Escape sequence of two delimiters
      (?P<named>%(id)s)      |   # delimiter and a Python identifier
      {(?P<braced>%(id)s)}   |   # delimiter and a braced identifier
      (?P<invalid>^$)            # never matches (the regex is not multilined)
    )
    """ % dict(delim=re.escape(Template.delimiter), id=Template.idpattern)


def check_substitution(template, **mapping):
    try:
        TemplateIgnoreInvalid(template).substitute(mapping)
    except KeyError:
        return False
    else:
        return True

测试

f = check_substitution
assert f('var is $var', var=1)
assert f('$ var is $var', var=1)
assert     f('var is $var and foo is $foo', var=1, foo=2)
assert not f('var is $var and foo is $foo', var=1)
assert     f('$ var is $var and foo is $foo', var=1, foo=2)
assert not f('$ var is $var and foo is $foo', var=1)
# support all invalid patterns
assert f('var is $var and foo is ${foo', var=1)
assert f('var is $var and foo is ${foo', var=1, foo=2) #NOTE: problematic API
assert     f('var is $var and foo is ${foo and ${baz}', var=1, baz=3)
assert not f('var is $var and foo is ${foo and ${baz}', var=1)

它适用于所有无效的分隔符 ($)。

这些示例表明,忽略无效模式会隐藏模板中的简单拼写错误,因此它不是一个好的 API。

【讨论】:

  • 完美!我将不得不考虑您所说的支持无效模式是一个坏主意。谢谢。
【解决方案3】:

这是一个快速修复(使用递归):

def check_substitution(tem, m):
    try:
        string.Template(tem).substitute(m)
    except KeyError:
        return False
    except ValueError:
        return check_substitution(tem.replace('$ ', '$'), m) #strip spaces after $
    return True

我知道如果 $var 之间有多个空格,则需要更长的时间,因此您可以使用正则表达式对其进行改进。

编辑

$ 转义为$$ 更有意义[感谢@Pedro],因此您可以通过以下语句捕获ValueError

return check_substitution(tem.replace('$ ', '$$ '), m) #escaping $ by $$

【讨论】:

  • 这个解决方案不是通用的。如果空格后面的字符不是字母,它仍然会失败,并假设$ 后面的空格是虚假的,并且原意是由$ 和空格后面的单词组成的占位符。通过加倍来逃避孤独的$ 会更有意义。
  • @Dantario 谢谢。不过,逃跑需要更多的工作。比如模板${foo也会触发ValueError,因为不平衡的{...这就是为什么我想避免自己解析模板,但也许没有其他选择。
  • @ErnestA: my answer 修复了${foo 问题。
猜你喜欢
  • 2015-09-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-01
  • 1970-01-01
  • 2016-10-28
  • 1970-01-01
相关资源
最近更新 更多