Python中带转义的递归字符串替换
我写了一个简单的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 个回答
我遇到过类似的问题,这是我解决它的方法。
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__
,就清空这个缓存。至少这是我在原始代码中所做的。