在Python的re模块中,如何匹配正则表达式组(使用或运算符)和特殊字符?

3

我正在尝试分离以下标题中的字符串,匹配名称和任何附加信息,无论是在括号/花括号中还是在破折号(常规、m/n破折号、水平线)之后。

正则表达式对我来说看起来很好,并且我能够在其他正则表达式测试器上测试它,但在Python中运行时却不起作用。

有几件奇怪的事情正在发生。第一个带破折号的标题似乎已经匹配了,但addition_a组并没有包含正确的字符串。此外,由于某种原因,任何特殊字符(如各种破折号)都无法匹配。脚本的编码是utf-8,所以我认为原始正则表达式字符串中的破折号应该可以正常工作,但它们没有。

# -*- coding: utf-8 -*-
import re
titles = [
    'Spaced (News)',
    'Angry Birds [Game]',
    'Cheats - for all games', # dash
    'Cheats – for all games', # ndash
    'Cheats — for all games', # mdash
    'Cheats ― for all games'  # horizontal bar
]
regex = re.compile(r'^(?P<name>.+)\s+(([-–—―]\s+(?P<addition_a>.+))|([\(\[](?P<addition_b>.+)[\)\]]))$')
for title in titles:
    data = {}
    match = regex.match(title.strip())
    if match:
        data['name'] = match.group('name')
        try:
            data['addition'] = match.group('addition_a')
        except IndexError:
            pass
        try:
            data['addition'] = match.group('addition_b')
        except IndexError:
            pass
    print data

输出:

{'addition': 'News', 'name': 'Spaces'}
{'addition': 'Game', 'name': 'Angry Birds'}
{'addition': None, 'name': 'Cheats'}
{}
{}
{}

糟糕,我发现我在使用数字索引访问组时忘记捕获IndexError异常了!唉。 - Michael Waterfall
3个回答

3

使用Unicode字面值。否则,[-–—―]将匹配-\xe2\x80\x93\xe2\x80\x94\xe2\x80\x95,而不是-

# -*- coding: utf-8 -*-
import re
titles = [
    u'Spaced (News)',
    u'Angry Birds [Game]',
    u'Cheats - for all games', # dash
    u'Cheats – for all games', # ndash
    u'Cheats — for all games', # mdash
    u'Cheats ― for all games'  # horizontal bar
]
regex = re.compile(ur'^(?P<name>.+)\s+(([-–—―]\s+(?P<addition_a>.+))|([\(\[](?P<addition_b>.+)[\)\]]))$')
for title in titles:
    match = regex.match(title.strip())
    if match:
        data = {}
        data['name'] = match.group('name')
        data['addition'] = match.group('addition_a') or match.group('addition_b')
        print data

输出:

{'addition': u'News', 'name': u'Spaced'}
{'addition': u'Game', 'name': u'Angry Birds'}
{'addition': u'for all games', 'name': u'Cheats'}
{'addition': u'for all games', 'name': u'Cheats'}
{'addition': u'for all games', 'name': u'Cheats'}
{'addition': u'for all games', 'name': u'Cheats'}

>>> r'[–]'
'[\xe2\x80\x93]'
>>> re.findall(r'[–]', '–')
['\xe2', '\x80', '\x93']
>>> re.findall(ur'[–]', u'–')
[u'\u2013']
>>> print re.findall(ur'[–]', u'–')[0]
–

啊,这很有道理,我不知道你可以指定一个字符串字面量为Unicode原始字符串!当我按索引而不是按名称访问组时,我也不小心留下了那些try/catch块。对于添加组使用“or”更加简洁;-)感谢您的帮助! - Michael Waterfall
1
@MichaelWaterfall,我忘了提到match.group('addition_a')总是会返回一些东西(str/None),即使没有addition_a组匹配。addition_b也是如此。 - falsetru

2
Unicode有一些“字符”或“符号”需要占用多个字节,Python在理解这个概念方面不太好,有时会出现问题。你可以尝试以下措施之一:
如果您能控制要解析的所有字符串,可以尝试确保它们都是Unicode编码的。对于您的示例,请在字符串开头添加“u”指示符即可:
u'Spaced (News)',
u'Angry Birds [Game]',
u'Cheats - for all games', # dash
u'Cheats – for all games', # ndash
u'Cheats — for all games', # mdash
u'Cheats ― for all games'  # horizontal bar

并将其添加到您的正则表达式中:

ur'^(?P<name>.+)\s+(([-–—―]\s+(?P<addition_a>.+))|([\(\[](?P<addition_b>.+)[\)\]]))$'

否则,或者如果您没有控制权,您可以进行一次小的修改 - 虽然不完全正确,但会起作用。这种变化是接受来自集合[-–—―]中的多个字符,而不仅仅是一个字符,方法是使用[-–—―]+
r'^(?P<name>.+)\s+(([-–—―]+\s+(?P<addition_a>.+))|([\(\[](?P<addition_b>.+)[\)\]]))$'

无论哪个选项,都会得到您想要的结果。

第一个选项将产生Unicode结果:

>>> 
{'addition': u'News', 'name': u'Spaced'}
{'addition': u'Game', 'name': u'Angry Birds'}
{'addition': None, 'name': u'Cheats'}
{'addition': None, 'name': u'Cheats'}
{'addition': None, 'name': u'Cheats'}
{'addition': None, 'name': u'Cheats'}

在常规字符串中的第二个位置:

>>> 
{'addition': 'News', 'name': 'Spaced'}
{'addition': 'Game', 'name': 'Angry Birds'}
{'addition': None, 'name': 'Cheats'}
{'addition': None, 'name': 'Cheats'}
{'addition': None, 'name': 'Cheats'}
{'addition': None, 'name': 'Cheats'}

谢谢这个解释,非常清晰易懂!我之前不知道还可以有Unicode原始字符串。每天都能学到新东西啊;-) - Michael Waterfall

2

还有一种稍微“震荡”一些的方法是将整个正则表达式改为“一些单词和空格,直到不再匹配,然后匹配剩余部分”。这也避免了可选的additional_aadditional_b命名组以及try/except逻辑。

示例:

for title in titles:
    data = dict(zip(['name', 'addition'], (m.strip() for m in re.findall('([\w\s]+)', title))))
    print data

输出:

{'addition': 'News', 'name': 'Spaced'}
{'addition': 'Game', 'name': 'Angry Birds'}
{'addition': 'for all games', 'name': 'Cheats'}
{'addition': 'for all games', 'name': 'Cheats'}
{'addition': 'for all games', 'name': 'Cheats'}
{'addition': 'for all games', 'name': 'Cheats'}

这真的很有趣。虽然它不完全适合我的需求,但它确实展示了Python是多么令人惊叹!感谢这个。 - Michael Waterfall

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