使用自定义占位符、对象的dotaccess和错误容差设置字符串格式

2024-10-03 15:23:58 发布

您现在位置:Python中文网/ 问答频道 /正文

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

  • 占位符的格式必须是$(...)
  • 它必须像str.format那样支持对象的“点访问”,例如'hey {obj.name}'.format(obj=some_object)
  • 每当在替换中找不到占位符时,我不希望它崩溃,而是继续使用其他占位符。在

我以为模块string中的Template类可以做到这一点:它实现了第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

在没有第三方库或不编写自己的代码的情况下,有没有办法做到这一点?在


Tags: inpyselfobjformatconvertstringlib
2条回答

只是翻译格式字符串?在

def translate(fmt):
    # escape their markers
    fmt = fmt.replace('{', '{{').replace('}', '}}')

    # translate our markers
    fmt = re.sub(r'\$\((.+?)\)', r'{\1}', fmt)

    # unescape out markers
    fmt = fmt.replace('$$', '$')

    return fmt

obj = lambda: this_is_a_hack
obj.it = 123
translate("$(testing.it)").format(testing=obj)

您可以重写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)

演示:

^{pr2}$

safe_subsitute方法的变化是,如果.存在于标识符中,则尝试使用reduce和{}计算其值:

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,如果您不希望这样做,请从regex中删除named组,并将函数convert的第一行更改为:

named = mo.group('braced')

相关问题 更多 >