在Python中使用任意值替换命名捕获组

5
我需要替换正则表达式中捕获组内的值为任意值;我已经查看了 re.sub,但它似乎工作方式不同。
我有一个像这样的字符串:
s = 'monthday=1, month=5, year=2018'

我有一个正则表达式,可以通过捕获组来匹配它,如下所示:

regex = re.compile('monthday=(?P<d>\d{1,2}), month=(?P<m>\d{1,2}), year=(?P<Y>20\d{2})')

现在我想要将名为d的组替换为aaa,将名为m的组替换为bbb,将名为Y的组替换为ccc,就像下面的例子一样:

'monthday=aaa, month=bbb, year=ccc'

基本上我想保留所有不匹配的字符串,并用一些任意值替换匹配组。

有没有办法实现所需的结果?

注意

这只是一个例子,我可能有其他具有不同结构但具有相同名称捕获组的输入正则表达式...

更新

由于似乎大多数人都关注示例数据,我添加了另一个示例,假设我有这个其他输入数据和正则表达式:

input = '2018-12-12'
regex = '((?P<Y>20\d{2})-(?P<m>[0-1]?\d)-(?P<d>\d{2}))'

正如您所看到的,我仍然有相同数量(3)和相同命名方式的捕获组,但结构完全不同... 然而,我需要的是像以前一样用一些任意文本替换捕获组:

'ccc-bbb-aaa'

将名为Y的捕获组替换为ccc,名为m的捕获组替换为bbb,名为d的捕获组替换为aaa

在这种情况下,正则表达式不是完成任务最好的工具,我愿意接受其他可以实现我的目标的建议。


1
regex.sub('monthday=aaa, month=bbb, year=ccc', s) - Aran-Fey
3
使用您的解决方案,我需要硬编码新结果,但这不是我要求的... 我想用一些任意值替换匹配组。 这只是一个例子,我可能有其他输入正则表达式具有不同结构,但名称捕获组相同... - aleroot
2
@Rawing,请仔细阅读问题的第一行:“我需要用任意值替换正则表达式中捕获组内的值”,这并不是你目前给出的解决方案所实现的。 - aleroot
1
你使用捕获组来复制字符串中想要替换的部分,而不是想要替换的部分。正则表达式不是一个模板机制。 - Barmar
1
@Barmar,那么你建议我如何实现我需要的功能? - aleroot
显示剩余7条评论
4个回答

9
这完全是一种错误的正则表达式使用方法。捕获组的目的是要保留你想要“保留”的文本,而不是要替换的文本。
由于您的正则表达式写错了,所以必须手动完成大部分替换操作:
"""
Replaces the text captured by named groups.
"""
def replace_groups(pattern, string, replacements):
    pattern = re.compile(pattern)
    # create a dict of {group_index: group_name} for use later
    groupnames = {index: name for name, index in pattern.groupindex.items()}

    def repl(match):
        # we have to split the matched text into chunks we want to keep and
        # chunks we want to replace
        # captured text will be replaced. uncaptured text will be kept.
        text = match.group()
        chunks = []
        lastindex = 0
        for i in range(1, pattern.groups+1):
            groupname = groupnames.get(i)
            if groupname not in replacements:
                continue

            # keep the text between this match and the last
            chunks.append(text[lastindex:match.start(i)])
            # then instead of the captured text, insert the replacement text for this group
            chunks.append(replacements[groupname])
            lastindex = match.end(i)
        chunks.append(text[lastindex:])
        # join all the junks to obtain the final string with replacements
        return ''.join(chunks)

    # for each occurence call our custom replacement function
    return re.sub(pattern, repl, string)

>>> replace_groups(pattern, s, {'d': 'aaa', 'm': 'bbb', 'Y': 'ccc'})
'monthday=aaa, month=bbb, year=ccc'

1
+1 for This is a completely backwards use of regex. The point of capture groups is to hold text you want to keep, not text you want to replace. This fixed my mental model and my problem, too.这是完全错误的正则表达式使用方式。捕获组的目的是保存你想要保留的文本,而不是替换文本。这修正了我的思维模式和问题。 - Christian

2
你可以使用字符串格式化和正则表达式替换来完成:
import re
s = 'monthday=1, month=5, year=2018'
s = re.sub('(?<=\=)\d+', '{}', s).format(*['aaa', 'bbb', 'ccc'])

输出:

'monthday=aaa, month=bbb, year=ccc'

编辑:给定任意输入字符串和正则表达式,您可以使用如下格式:

input = '2018-12-12'
regex = '((?P<Y>20\d{2})-(?P<m>[0-1]?\d)-(?P<d>\d{2}))'
new_s = re.sub(regex, '{}', input).format(*["aaa", "bbb", "ccc"])

这似乎是位置相关的... 如果输入和相关的正则表达式改为以下格式:year=2018,monthday=1,month=5,会怎样?如已写明,不要太在意示例数据,问题的要求是:“我需要将正则表达式捕获组中的值替换为任意值”。所提出的解决方法似乎没有做到这一点... - aleroot
请看更新后的答案,应该会澄清我需要什么,以及问题实际上在问什么。谢谢。 - aleroot
@aleroot,看起来你发布的输入和匹配的组相当随意。我建议你从模板化的角度来解决这个问题。 - Ajax1234

2

在扩展示例中,对于Python 3.x的解决方案进行了扩展,使用了re.sub()替换函数:

import re

d = {'d':'aaa', 'm':'bbb', 'Y':'ccc'}  # predefined dict of replace words
pat = re.compile('(monthday=)(?P<d>\d{1,2})|(month=)(?P<m>\d{1,2})|(year=)(?P<Y>20\d{2})')

def repl(m):
    pair = next(t for t in m.groupdict().items() if t[1])
    k = next(filter(None, m.groups()))  # preceding `key` for currently replaced sequence (i.e. 'monthday=' or 'month=' or 'year=')
    return k + d.get(pair[0], '')

s = 'Data: year=2018, monthday=1, month=5, some other text'
result = pat.sub(repl, s)

print(result)

输出:
Data: year=ccc, monthday=aaa, month=bbb, some other text

对于Python 2.7版本: 将k = next(filter(None, m.groups()))这一行更改为:
k = filter(None, m.groups())[0]

k = next(filter(None, m.groups())) # 当前替换序列的前一个“键”(即“monthday=”或“month=”或“year=”) TypeError: 元组对象不是迭代器 我在使用Python 2.7。 - aleroot
我使用的是Python 2.7版本,有没有办法让它在这个版本上运行?因为现在我无法升级。 - aleroot

0

我建议您使用循环

import re
regex = re.compile('monthday=(?P<d>\d{1,2}), month=(?P<m>\d{1,2}), year=(?P<Y>20\d{2})')
s = 'monthday=1, month=1, year=2017   \n'
s+= 'monthday=2, month=2, year=2019'


regex_as_str =  'monthday={d}, month={m}, year={Y}'
matches = [match.groupdict() for match in regex.finditer(s)]
for match in matches:
    s = s.replace(
        regex_as_str.format(**match),
        regex_as_str.format(**{'d': 'aaa', 'm': 'bbb', 'Y': 'ccc'})
    )    

你可以使用不同的正则表达式模式多次执行此操作

或者你可以将这两个模式合并("或")在一起


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接