使用自定义占位符,对象的点访问和容错来格式化字符串

时间:2015-05-26 16:03:49

标签: python string templates

我需要填写一个小字符串模板。我的要求如下:

  • 占位符的格式必须为$(...)
  • 它必须支持" dot-access"对于对象,如str.format所做的那样,例如在'hey {obj.name}'.format(obj=some_object)
  • 每当在替换中找不到占位符时,我都不希望它崩溃,但继续使用其余的。

我认为模块Template中的string类可以解决问题:它实现了第1点(占位符)和3(容错),但不幸的是它不支持"点接入"

>>> from string import Template
>>> t = Template('$(obj.get)')
>>> t.substitute(obj=dict)
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "C:\Development\CDBServer_active\lib\string.py", line 172, in substitute
    return self.pattern.sub(convert, self.template)
  File "C:\Development\CDBServer_active\lib\string.py", line 169, in convert
    self._invalid(mo)
  File "C:\Development\CDBServer_active\lib\string.py", line 146, in _invalid
    (lineno, colno))
ValueError: Invalid placeholder in string: line 1, col 1

如果没有第三方库或没有编写我自己的代码,有没有办法做到这一点?

2 个答案:

答案 0 :(得分:1)

只需翻译格式字符串?

// your object
var o = {
    "numbers": [
        {"id":"11111"},
        {"id":"22222"}
    ],
    "letters": [
        {"id":"aaaaa"},
        {"id":"bbbbb"},
        {"id":"33333"}
    ]
};

// gets the index
// a = the array to look in
// p = the property to check
// v = the value you are looking for
function getIndex(a, p, v) {
    for(var i=0; i < a.length; i++) {
        if(a[i][p] == v) {
            return i;
        }
    }
}

// get the index from the letters array with property 'id' with value '33333'
var index = getIndex(o['letters'], 'id', '33333');
console.log(index);
// if you found the item
if (index !== undefined) {
    // get the actual item from the array
    var item = o['letters'][index];
    console.log(o['letters']);
    console.log(o['numbers']);
    console.log(item);
    // add the item to the other numbers array
    o['numbers'].push(item);
    // remove the item from the letters array
    o['letters'].splice(index, 1);
    console.log(o['letters']);
    console.log(o['numbers']);
} else {
    console.log('did not find the item');
}

答案 1 :(得分:1)

您可以覆盖Template类并定义自己的模式和safe_substitute方法以获得所需的行为:

from string import Template


class MyTemplate(Template):
    pattern = r"""
    \$(?:
      (?P<escaped>\$)|   # Escape sequence of two delimiters
      (?P<named>[_a-z][_.a-z0-9]*)|   # delimiter and a Python identifier
      \((?P<braced>[_a-z][_.a-z0-9]*)\)|   # delimiter and a braced identifier
      (?P<invalid>)              # Other ill-formed delimiter exprs
    )
    """

    def safe_substitute(*args, **kws):
        if not args:
            raise TypeError("descriptor 'safe_substitute' of 'Template' object "
                            "needs an argument")
        self, args = args[0], args[1:]  # allow the "self" keyword be passed
        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('braced')
            if named is not None:
                try:
                    if '.' not in named:
                        return '%s' % (mapping[named],)
                    else:
                        attrs = named.split('.')
                        named, attrs = attrs[0], attrs[1:]
                        if not named.strip() or not all(attr.strip() for attr in attrs):
                            #  handle cases like foo. foo.bar..spam
                            raise Exception()
                        return '%s' % reduce(lambda x, y: getattr(x, y), attrs, mapping[named])
                except Exception as e:
                    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)

<强>演示:

class A(object):
    def __init__(self, val):
        self.val = val
    def __getattr__(self, attr):
        return A(self.val * 2)
    def __repr__(self):
        return str(self.val)

>>> t = MyTemplate('$(obj.get) $(foo) $(spam.a.b.c.d) $(A.__getattr__)')
>>> t.safe_substitute(obj=dict, foo=1, spam=A(10), A=A)
"<method 'get' of 'dict' objects> 1 160 <unbound method A.__getattr__>"
>>> t.safe_substitute(obj=A(100), foo=1, spam=A(10), A=A)
'200 1 160 <unbound method A.__getattr__>'

safe_subsitute方法中的更改是,如果标识符中存在.,则尝试使用reduce getattr计算其值:

attrs = named.split('.')
named, attrs = attrs[0], attrs[1:]
if not named.strip() or not all(attr.strip() for attr in attrs):
    #  handle cases like foo. foo.bar..spam 
    raise Exception()
return '%s' % reduce(lambda x, y: getattr(x, y), attrs, mapping[named])

请注意,如果您不希望该行为从正则表达式中删除$obj.get组并更改函数的第一行{},则当前代码也将替换named等命名组的值。 {1}}:

convert