Python正则表达式中的前瞻断言和分组

3

假设我有很多行文本,比如这一行:

row = '   S.G. Primary School\t\t 434,612.50'

我希望找到一个看起来像会计格式的数字,然后向前查找并提取该数字前面的单词或短语。以下是该数字的格式:

test = re.search(r"""(?=((-?\d{1,3})(,\d{3})*(\.\d\d)?$|^\.\d\d$))""",row)
   S.G. Primary School       434,612.50
test.groups()
('434,612.50', '434', ',612', '.50')

看起来正确。我有完整的数字和它的各个部分(我都想要)。但是我无法通过先行断言找到数字前面的单词(或短语)。

我尝试了:

test = re.search(r"""([A-Za-z ].*) (?=((-?\d{1,3})(,\d{3})*(\.\d\d)?$|^\.\d\d$))""",row)
('   S.G. Primary School\t\t', '434,612.50', '434', ',612', '.50')

我本周花了4个小时重新阅读正则表达式文档,但我仍然不知道自己是否有所进展。示例似乎对我不起作用。我不能使用\ w +,因为我只想要标签是纯文本和空格,但我还想从匹配数字的开始位置开始倒数计数。这听起来像是“正向先行断言”,其一般格式为“\w +(?= \ d)”,但这对我不起作用。
此外-我对分配多个前瞻断言的正确方法感到困惑,所有这些断言在匹配返回之前都必须为真:

r"""([A-Za-z ]*)(.*?)([\d,.]+)(?=[A-Za-z ]*)(?=[\d,.])"""

与...有所不同

r"""([A-Za-z ]*)(?=[A-Za-z ]*)(.*?)([\d,.]+)(?=[\d,.])"""

因为在这个例子中两者得到的结果相同:

('   S', '.G. Primary School\t\t ', '434,612.5')

更新

这里有三个例子,我正在努力找到正则表达式的答案:

import re
rows = ['   S.G. Primary School\t\t 434,612.50',
       '   S.G. Bad Primary School\t\t 434,612.50',
       '   N.3#=42^2492q\t\t\t 434,612.50']

for row in rows:
    test = re.search(r"""(?!\s)([A-Za-z]{0,25}) ?([a-zA-Z]{6,25}).*?(?=(?:(?:-?\d{1,3})(?:,\d{3})*(?:\.\d\d)?$|^\.\d\d$))((-?\d{1,3})(,\d{3})*(\.\d\d)?$|^\.\d\d$)""",row)
    if test != None:
        print test.groups()
    else:
        print test

这将返回:
('Primary', 'School', '434,612.50', '434', ',612', '.50')
('Bad', 'Primary', '434,612.50', '434', ',612', '.50')
None

I would like the result to be:

('Primary', 'School', '434,612.50', '434', ',612', '.50')
('Primary', 'School', '434,612.50', '434', ',612', '.50')
('', '434,612.50', '434', ',612', '.50')

我希望代码可以进行调整,以便我还可以返回以下内容:

('School', '434,612.50', '434', ',612', '.50')
('School', '434,612.50', '434', ',612', '.50')
('', '434,612.50', '434', ',612', '.50')

有修改。

更新

根据卡西米尔的答案,这样可以返回更好的数据,但我不明白如何获取数字之前的多个单词短语:

test = re.search(r'([A-Za-z][A-Za-z_.]*){1,2}\s+((-?\d{1,3})(,\d{3})*(\.\d\d)?$|^\.\d\d$)',row)
('School', '434,612.50', '434', ',612', '.50')
('School', '434,612.50', '434', ',612', '.50')
('q', '434,612.50', '434', ',612', '.50')

我不知道为什么

test = re.search(r'([A-Za-z_.]*){1,2}\s+((-?\d{1,3})(,\d{3})*(\.\d\d)?$|^\.\d\d$)',row)

发生错误:无法重复任何内容。我所做的只是改变了

标签。
[A-Za-z][A-Za-z_.]*){1,2} 

为了

[A-Za-z_.]*){1,2}

在第一组中。

也许:

test = re.search(r'([A-Za-z][A-Za-z_.]*){0,}\s+([A-Za-z][A-Za-z_.]*){0,}\s+((-?\d{1,3})(,\d{3})*(\.\d\d)?$|^\.\d\d$)',row)

更好,因为我能够得到第一个单词和第二个单词,但我不确定如何将它们组合起来并使它们变成可选项:

('Primary', 'School', '434,612.50', '434', ',612', '.50')
('Primary', 'School', '434,612.50', '434', ',612', '.50')
('q', None, '434,612.50', '434', ',612', '.50')

更新

我采用了Casimir的答案(稍作修改),将{0,2}更改为{0,1},并使用findall版本进行了测试:

import re
rows = ['   S.G. Primary School\t\t 434,612.50 S.G. Primary School\t\t 434,612.50',
       '   S.G. Bad Primary School\t\t 434,612.50 Bad Primary School\t\t 434,612.50',
       '   N.3#=42^2492q\t\t\t 434,612.50  N.3#=42^2492q\t\t\t 434,612.50  N.3#=42^2492q\t\t\t 434,612.50 ']

for row in rows:
    test = re.findall(r"(?i)([a-z][a-z_.]*(?:\s+[a-z][a-z_.]*){0,1})?\s+((-?\d{1,3})(?:,\d{3})*(?:\.\d\d)?$|^\.\d\d$)",row)
    test = re.findall(r"(?i)([a-z][a-z_.]*(?:\s+[a-z][a-z_.]*){0,1})?\s+(-?\d{1,3}(?:,\d{3})*(?:\.\d\d)?)",row)
    print test 

但第一个测试返回了这个结果(当第二个测试语句被注释掉时):

[('Primary School', '434,612.50', '434')]
[('Primary School', '434,612.50', '434')]
[]

第二个测试语句返回了我想要的结果,一个结果列表:

[('Primary School', '434,612.50'), ('Primary School', '434,612.50')]
[('Primary School', '434,612.50'), ('Primary School', '434,612.50')]
[('q', '434,612.50'), ('q', '434,612.50'), ('q', '434,612.50')]

但是这些陈述非常相似,我不知道为什么一个列表中缺少多个数字/标签。


1
我有点困惑。你不是在寻找 (' S.G. Primary School\t\t', '434,612.50', '434', ',612', '.50') 吗? - James Lim
所以你想匹配“S.G.小学”,而不消耗行的其余部分,但要检查它是否看起来像某些特定的东西(会计号码),对吧? - Alfe
这将按原样捕获第一个“短语”:re.search(r"""(\s+\D+\s+(?=((-?\d{1,3})(,\d{3})*(\.\d\d)?$|^\.\d\d$)))""",row).groups()。这是你想要的吗? - Doctor Dan
@MarcMaxson:为什么是“Primary School”,而不是“S.G. Primary School”? - Casimir et Hippolyte
是的-我想返回一个或两个单词,后面跟着一个用逗号和点格式化的数字,这是会计文件的典型方式。那么最好是双重前瞻(1:单词是单词,2:数字是跟在单词后面的数字)吗? - Marc Maxmeister
显示剩余6条评论
1个回答

0

你根本不需要向前查看:

(?i)([a-z][a-z_.]*(?:\s+[a-z][a-z_.]*){0,2})?\s+(-?\d{1,3}(?:,\d{3})*(?:\.\d\d)?)

使用{0,...}可以控制您想要的单词数量。如果您想要所有单词,请将其替换为*。如果您只想要一个单词,请删除所有非捕获组:

(?i)([a-z][a-z_.]*)?\s+(-?\d{1,3}(?:,\d{3})*(?:\.\d\d)?)

如果你想要恰好三个单词:

(?i)([a-z][a-z_.]*(?:\s+[a-z][a-z_.]*){2})\s+(-?\d{1,3}(?:,\d{3})*(?:\.\d\d)?)

如果你想避免“非单词”(如字母“q”)中的单个字母,可以添加:

(?i)((?:^|(?<=\s))[a-z][a-z_.]*(?:\s+[a-z][a-z_.]*){0,2})?\s+(-?\d{1,3}(?:,\d{3})*(?:\.\d\d)?)

模式细节:

(?i)                      # make the pattern case insensitive
(                         # open the first capturing group
    (?:^|(?<=\s))         # begining of the string or lookbehind with space
    [a-z][a-z_.]*         # a letter and zero or more chars from [a-z_.]
    (?:                   # open a non-capturing group
        \s+               # one or more spaces
        [a-z][a-z_.]*     # a letter and zero or more chars from [a-z_.]
    ){0,2}                # repeat the capturing group zero or two times
)?                        # close the capturing group and make it optional
\s+                       # one or more spaces
(                         # open a capturing group
    -?                    # - sign optional
    \d{1,3}               # between 1 or 3 digits
    (?:,\d{3})*           # a group (zero or more times) with a , and 3 digits
    (?:\.\d\d)?           # an optional group with a . and 2 digits
)                         # close the second capturing group.

我们还不知道呢 ;-) 他没有解释完整的情况。 - Alfe
1
是的,但我能感受到他精神上的联系,他不需要一个lookahead。 - Casimir et Hippolyte
我倾向于同意这一点;-)但是假设是通往地狱的道路。 - Alfe
通过您的回答,我已经找到了更接近的答案(返回“school”,但还没有返回两个单词的版本“Primary School”):r'([A-Za-z][A-Za-z_.]*){1,2}\s+((-?\d{1,3})(,\d{3})*(\.\d\d)?$|^\.\d\d$)' - Marc Maxmeister
@MarcMaxson:这不是我的模式。 - Casimir et Hippolyte
显示剩余3条评论

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