在Python中使用转义进行递归字符串替换

时间:2014-07-27 23:27:33

标签: python string recursion escaping formatter

我在Python中创建了一个简单的脚本,根据源目录结构生成一些项目。在其中我使用Formatter,因为它证明了使用字典(也嵌套!)替换字符串的能力非常方便。

然而现在,在扩展脚本时,我需要更复杂的替代品。首先,我想让替换是递归的。替换字段的字符串可能需要自己格式化(使用相同的参数)。其次,我需要能够根据提供的函数转义最终字符串(到目前为止唯一的用例是re.escape以逃避正则表达式。)

我在Python中寻找内置的东西,但没有找到任何有用的东西。 Formatter(如提供的那样)显然不符合这些标准。


我的第一次尝试是使用一个简单的函数,如:

def expand_vars(string, vars):
    while True:
        expanded = string.format(**vars)
        if expanded == string:
            break
        string = expanded
    return string

它只是一直调用format,直到字符串中没有任何更改(这意味着所有字段都被替换)。

然而,在这里嵌入逃脱并不容易。我只需要转义替换值(不是整个string),只有最终值(每次调用时转义都会导致多次转义字符串的某些部分)。

此功能的另一个问题是它可能无意中创建非字段的字段。当其中一个字段以字符串{a结尾而另一个字段在下一次迭代中以b}结尾时,我们会有意外的字段{ab}。 (嗯,它可以被认为是一个特征,但在我的情况下,我并没有这样认为。)


另一种方法是继承Formatter。我最终得到了类似的东西:

class RecursiveEscapingFormatter(Formatter):
    def __init__(self, escape=None):
        Formatter.__init__(self)
        self.escape = escape

    def get_field(self, field_name, args, kwargs):
        obj, arg_used = super(RecursiveEscapingFormatter, self).get_field(field_name, args, kwargs)
        if self.escape is None:
            nonEscapingFormatter = self
        else:
            nonEscapingFormatter = copy.copy(self);
            nonEscapingFormatter.escape = None
        obj = nonEscapingFormatter.vformat(obj, args, kwargs)
        return obj, arg_used

    def convert_field(self, value, conversion):
        result = super(RecursiveEscapingFormatter, self).convert_field(value, conversion)
        if self.escape is not None:
            result = self.escape(result)
        return result

现在的问题是我无法确保正确拨打check_unused_args。我没有看到合理的方式(=不要求整个类覆盖)跟踪get_field中递归调用使用的参数。我自己并不需要这个,但是制作一个合适的类(后来可能继承自......)需要正确处理check_unused_args。怎么做?


或许是否有更好的方法解决问题(使用转义进行递归替换)?

1 个答案:

答案 0 :(得分:2)

我遇到了类似的问题,这就是我如何解决它的问题。

from string import Formatter

class RecursivelySubstitutedDictionary:
    def __init__(self, dictionary):
        self.formatter = Formatter()
        self.dictionary = dictionary
        self.substituting = set([])

    def __getitem__(self, key):
        if(key in self.substituting):
            raise ValueError("Cyclic reference. Key: %s." % key)
        self.substituting.add(key)
        unsubstitutedval = self.dictionary[key]
        substitutedval = self.formatter.vformat(unsubstitutedval,[],self)
        self.substituting.remove(key)
        return substitutedval

使用示例

regulardict = {
    'hi': 'hello {arg}',
    'arg': '{arg_1}{arg_2}',
    'arg_1': 'wo',
    'arg_2': 'rld',
}

print RecursivelySubstitutedDictionary(regulardict)['hi']
# prints hello world



cyclicdict = {
    'hi': 'hello {arg}',
    'arg': '{hi}',
}

print RecursivelySubstitutedDictionary(cyclicdict)['hi']
# raises ValueError: Cyclic reference. Key: hi.

如果调用__setitem__,您还可以考虑缓存替换的val并清除缓存。至少这是我在原始代码中所做的事情。