有选择地替换字符串中特定的嵌套定界符(括号),同时尊重嵌套

Selectively replace specific nested delimiters (brackets) in strings, while respecting nesting

我有很多字符串,我试图有选择地将 f[--whatever--] 的所有实例替换为 f.__getitem__(--whatever--, x=x)。 这是留给我的最后一个选项,可以使用 eval 调用来修补一些旧的复杂代码,不幸的是,我一直坚持使用它。 替换 f[ 很容易,但很难知道 ] 的实例是否与此模式或其他一些模式相关联 杂项模式,如列表 [--whatever--] 或索引 .loc[--whatever--]. 在我的字符串中没有 ] 不属于完整 [] 的孤立情况。

我最近的解决方案尝试使用正则表达式: 1) sub ([^f])[(.+?)] with openbracketclosebracket 保留不属于 f[] 的 [] 2)剩下的[] 3) 使用 []

后退开括号和闭括号

问题是这不能处理很多嵌套情况,如下例所示。我正在寻找一个更全面的解决方案来确定给定的 ] 是否与 f[] 或其他一些结构相关联。有没有办法用 pyparsing 或其他模块做到这一点?

例子

f[r@ndom t3xt] + [some r@ndom t3xt] + [f[more r@ndom t3xt] / f[more t3xt]] + [f[f[more t3xt] + 3]]

应该变成

f.__getitem__(r@ndom t3xt, x=x) + [some r@ndom t3xt] + [f.__getitem__(more r@ndom t3xt, x=x) / f.__getitem__(more t3xt, x=x)] + [f.__getitem__(f.__getitem__(more t3xt) + 3)]

也许,

f\[([^]]*)\]

re.sub 以及

f.__getitem__(, x=x)

可能只是工作。

测试

import re

regex = r"f\[([^]]*)\]"

string = """
f[r@ndom t3xt] + [some r@ndom t3xt] + [f[more r@ndom t3xt] / f[more t3xt]]
f[] + [] + [f[] / f[]]

"""

subst = "f.__getitem__(, x=x)"

print(re.sub(regex, subst, string))

输出

f.__getitem__(, x=x) + [some r@ndom t3xt] + [f.__getitem__(, x=x) / f.__getitem__(, x=x)]
f.__getitem__(, x=x) + [] + [f.__getitem__(, x=x) / f.__getitem__(, x=x)]

如果您希望 simplify/modify/explore 表达式,regex101.com. If you'd like, you can also watch in this link 的右上面板已对其进行说明,它将如何匹配一些示例输入。


嵌套的 [] 使这成为一个重要的问题。 pyparsing 有一个名为 nestedExpr 的 "crutch" 表达式方法,可以轻松匹配 () 和 [] 等嵌套分隔符。 pyparsing 也有 transformString 方法,用于将解析后的数据转换为不同的形式。我们可以使用解析时回调(或 "parse action")重复转换任何嵌套的 f[zzz] 项,直到所有项都已转换:

import pyparsing as pp

fname = pp.Keyword('f')
index_expr = pp.nestedExpr('[', ']')
# nestedExpr will give a nested list by default, we just want the original raw text
f_expr = fname + pp.originalTextFor(index_expr)("index_expr")

# define a parse action to convert the f[aaa] format to f._getitem__(aaa, x=x)
def convert_to_getitem(t):
    # get the contents of the index_expr, minus the leading and trailing []'s
    index_expr = t.index_expr[1:-1]

    # repeatedly call transform string to get further nested f[] expressions, until 
    # transformString stops returning a modified string
    while True:
        transformed = f_expr.transformString(index_expr)
        if transformed == index_expr:
            break
        index_expr = transformed

    # reformat to use getitem
    return "f.__getitem__({}, x=x)".format(transformed)

# add the parse action to f_expr
f_expr.addParseAction(convert_to_getitem)


# use transformString to convert the input string with nested expressions
sample = "f[r@ndom t3xt] + [some r@ndom t3xt] + [f[more r@ndom t3xt] / f[more t3xt]] + [f[f[more t3xt] + 3]]"
print(f_expr.transformString(sample))

打印:

f.__getitem__(r@ndom t3xt, x=x) + [some r@ndom t3xt] + [f.__getitem__(more r@ndom t3xt, x=x) / f.__getitem__(more t3xt, x=x)] + [f.__getitem__(f.__getitem__(more t3xt, x=x) + 3, x=x)]

这还应该处理可能出现在带引号的字符串中的“[]”。

使用正则表达式的解决方案:

import re

string1 = "f[r@ndom t3xt] + [some r@ndom t3xt] + 3[f2[more r@ndom t3xt] / f[more t3xt]] + [f[f[more t3xt] + 3]]"
string3 = '''f[text([0,[1,2],3, x["text3"]])]'''


def get_repl(match):
    if match.groups()[-1]:
        # replace nested [ and ]  with special characters
        return match.groups()[-1].replace('[', '##1##').replace(']', '##2##')
    else:
        return '{}.__getitem__({}, x=x)'.format(*match.groups()[:-1])

def place_by_getitem(string):
    pattern = '(?<!\w)(f)\[([^\[]+?)\]|(\[[^\[]+?\])'
    while re.search(pattern, string):
        string = re.sub(pattern, get_repl, string)

    return string.replace('##1##', '[').replace('##2##', ']')


print(place_by_getitem(string1))
print(place_by_getitem(string3))

输出:

f.__getitem__(r@ndom t3xt, x=x) + [some r@ndom t3xt] + 3[f2.__getitem__(more r@ndom t3xt, x=x) / f.__getitem__(more t3xt, x=x)] + [f.__getitem__(f.__getitem__(more t3xt, x=x) + 3, x=x)]
f.__getitem__(text([0,[1,2],3, x.__getitem__("text3", x=x)]), x=x)