2

我尝试将一个字符串标记"spam bar ds<hai bye>sd baz eggs"为一个列表['spam', 'bar', 'ds<hai bye>sd', 'baz', 'eggs'],即喜欢str.split()但保留里面的空格< ... >

我的解决方案是使用re.split模式(\S*<.*?>\S*)|\s+。但是我得到以下信息:

>>> re.split('(\S*<.*?>\S*)|\s+', "spam bar ds<hai bye>sd baz eggs")
['spam', None, 'bar', None, '', 'ds<hai bye>sd', '', None, 'baz', None, 'eggs']

不确定那些Nones 和空字符串来自哪里。当然,我可以使用 list comprehension 将它们过滤掉[s for s in result if s],但在我知道原因之前我不习惯这样做。

那么,(1)为什么那些Nones 和空字符串,(2)可以做得更好吗?

4

3 回答 3

3

None空字符串值是因为您在模式中使用了捕获括号,因此拆分包含匹配的文本 - 请参阅官方文档以了解这一点。

如果您将模式修改为r"((?:\S*<.*?>\S*)|\S+")(即转义括号以使其不捕获并将空白更正为非空白)它应该可以工作,但只能通过保留定界符,然后您需要通过跳过替代项来过滤掉它。我认为你最好这样做:

ITEM_RE = re.compile(r"(?:\S*<.*?>\S*)|\S+")
ITEM_RE.findall("spam bar ds<hai bye>sd baz eggs")

如果您不需要一个实际列表(即您一次只浏览一项),那么finditer()效率更高,因为它一次只产生一项。如果您可能在不查看整个列表的情况下退出,则尤其如此。

原则上也可能使用否定的后向断言,但实际上我认为创建一个足够灵活的断言是不可能的 - 我尝试r"(?<!<[^>]*)\s+"并得到错误“后向需要固定宽度模式”,所以我猜那是不可以。文档证实了这一点 - 后向断言(正面和负面)都需要固定宽度。

这种方法的问题在于,如果您期望嵌套尖括号 - 那么您将不会得到您期望的结果。例如,解析ds<hai <bye> foo>sd将产生ds<hai <bye>一个标记。我认为这是正则表达式无法解决的一类问题——你需要更接近正确解析器的东西。用纯 Python 编写一个一次遍历字符并计算括号嵌套级别的代码并不难,但这会很慢。取决于您是否可以确定您只会在输入中看到一层嵌套。

于 2013-03-07T20:19:51.587 回答
1

我得到了这个正则表达式:

ss = "spam bar ds<hai bye>sd baz eggs ZQ<boo <abv> foo>WX  "

reg = re.compile('(?:'
                     '\S*?'
                     '<'
                     '[^<>]*?'
                     '(?:<[^<>]*>[^<>]*)*'
                     '[^<>]*?'
                     '>'
                       ')?'
                 '\S+')

print reg.findall(ss)

结果

['spam', 'bar', 'ds<hai bye>sd', 'baz', 'eggs',
 'ZQ<boo <abv> foo>WX']

编辑 1

在 Cartroo 的评论之后,一个更准确的新正则表达式:

import re

pat = ('(?<!\S)'  # absence of non-whitespace before

       '(?:'
           '[^\s<>]+'

           '|'  # OR

           '(?:[^\s<>]*)'
           '(?:'
               '<'
               '[^<>]*?'
               '(?:<[^<>]*?>[^<>]*)*'
               '[^<>]*?'
               '>'
               ')'
           '(?:[^\s<>]*)'
       ')'

       '(?!\S)' # absence of non-whitespace after)
       )
reg = re.compile(pat)

ss = ("spam i>j bar ds<hai bye>sd baz eggs Z<boo <abv>"
      " foo>W ttt <two<;>*<:> three> ")
print '%s\n' % ss
print reg.findall(ss)

ss = "a<b<E1>c>d <b<E2>c>d <b<E3>c> a<<E4>c>d <<E5>>d 
   <<E6>> <<>>"
print '\n\n%s\n' % ss
print reg.findall(ss)

结果

spam i>j bar ds<hai bye>sd baz eggs Z<boo <abv> foo>W 
ttt <two<;>*<:> three> 

['spam', 'bar', 'ds<hai bye>sd', 'baz', 'eggs', 
 'Z<boo <abv> foo>W', 'ttt', '<two<;>*<:> three>']


a<b<E1>c>d <b<E2>c>d <b<E3>c> a<<E4>c>d <<E5>>d <<E6>> <<>>

['a<b<E1>c>d', '<b<E2>c>d', '<b<E3>c>', 'a<<E4>c>d', '<<E5>>d',
 '<<E6>>', '<<>>']

上述字符串形成良好,结果一致。
在格式不正确的文本上(关于括号),它可能会给出非预期的结果:

ss = """A<B<C>D  
 E<F<G>H 
I<J>K> 
 L<<M>N
   O<P>>Q
 R<<S>    T<<>"""
print '\n\n%s\n' % ss
print reg.findall(ss)

结果

A<B<C>D  
 E<F<G>H 
I<J>K> 
 L<<M>N
   O<P>>Q
 R<<S>    T<<>

['E<F<G>H \nI<J>K>', 'L<<M>N\n   O<P>>Q']

那是因为末尾的星号'(?:<[^<>]*?>[^<>]*)*'。可以通过移除星号来关闭此行为。这种行为使得难以使用正则表达式来分析这种“复杂”的文本,正如 Crtaroo 所称的那样。

.

编辑 2

当我说结果是不想要的结果时'E<F<G>H \nI<J>K>''L<<M>N\n O<P>>Q'这并不意味着找到的匹配部分不尊重正则表达式的模式(怎么可能?),因为我制作了它;匹配部分形成良好,确实:
两个部分<G><J>位于两个支架之间< <G> <J> >
两个部分<M><P>位于两个支架之间< <M> <P> >

事实上,这是一种轻描淡写的说法,意味着找到的每个匹配部分都应该只延伸一行。但是,一旦轻描淡写地表达出来,就会出现一个可能的解决方案。
如果不需要在多行上延伸的匹配部分,很容易告诉正则表达式不匹配它们,这与我写的相反。\n在正则表达式模式的某些地方添加字符就足够了。

事实上,这意味着匹配部分不能越过一个\n字符,然后这个字符可以被认为是匹配部分的分隔符。因此,可能需要任何其他字符作为同一行上存在的匹配部分之间的分隔符,例如#在以下代码中。

.

正则表达式不能做饭或从学校接孩子,但它们非常强大。说正则表达式对格式错误的文本的行为是一个问题太短了:必须补充说这是文本的问题,而不是正则表达式。一个正则表达式做它被命令做的事情:吃掉给它的任何文本。它贪婪地吃掉它,也就是说,在没有验证任何一致性的情况下,这不是它的预期行为,如果它被喂食了不合饮食的文字,它是不负责任的。说正则表达式在格式错误的文本上的行为是一个问题,听起来好像有人会责备孩子有时会用威士忌和胡椒食品来滋养。

编码员有责任确保传递给正则表达式的文本格式正确。就像编码人员将验证片段放入代码中以确保条目是整数以便程序正确运行一样。

.

这一点与试图将标记文本解析为 XML 文本时滥用正则表达式不同。正则表达式无法解析这样的文本,好吧,因为不可能制作出能够对格式错误的标记文本做出正确反应的正则表达式。不尝试这样做也是编码人员的责任。这并不意味着如果该文本已经过验证,则
不得使用正则表达式来分析标记文本。
无论如何,如果文本格式错误太多,即使解析器也不会捕获数据。

我的意思是我们必须区分:

  • 传递给正则表达式的文本的性质(格式错误/格式正确)

  • 使用正则表达式时所追求目标的性质(解析/分析)

.

import re

ss = """
 A<:<11>:<12>:>
 fgh
 A<#:<33>:<34>:>
 A#<:<55>:<56>:>
 A<:<77>:<78> i<j>
 A<B<C>D #
 E<F<G>H #
 I<J>K> 
 L<<M>N 
 O<P>>Q  #
 R<<S>  T<<>"""
print '%s\n' % ss

pat = ('(?<!\S)'  # absence of non-whitespace before
           '(?:[^\s<>]*)'
           '(?:<'
               '[^<>]*?'
               '(?:<[^<>]*?>[^<>]*)*'
               '>)'
           '(?:[^\s<>]*)'
       '(?!\S)' # absence of non-whitespace after)
       )
reg = re.compile(pat)
print '------------------------------'
print '\n'.join(map(repr,reg.findall(ss)))


pat = ('(?<!\S)'  # absence of non-whitespace before
           '(?:[^\s<>]*)'
           '(?:<'
               '[^<>\n]*?'
               '(?:<[^<>\n]*?>[^<>\n]*)*'
               '>)'
           '(?:[^\s<>]*)'
       '(?!\S)' # absence of non-whitespace after)
       )
reg = re.compile(pat)
print '\n----------- with \\n -------------'
print '\n'.join(map(repr,reg.findall(ss)))


pat = ('(?<!\S)'  # absence of non-whitespace before
           '(?:[^\s<>]*)'
           '(?:<'
               '[^<>#]*?'
               '(?:<[^<>#]*?>[^<>#]*)*'
               '>)'
           '(?:[^\s<>]*)'
       '(?!\S)' # absence of non-whitespace after)
       )
reg = re.compile(pat)
print '\n------------- with # -----------'
print '\n'.join(map(repr,reg.findall(ss)))


pat = ('(?<!\S)'  # absence of non-whitespace before
           '(?:[^\s<>#]*)'
           '(?:<'
               '[^<>#]*?'
               '(?:<[^<>#]*?>[^<>#]*)*'
               '>)'
           '(?:[^\s<>]*)'
       '(?!\S)' # absence of non-whitespace after)
       )
reg = re.compile(pat)
print '\n------ with ^# everywhere -------'
print '\n'.join(map(repr,reg.findall(ss)))

结果

 A<:<11>:<12>:>
 fgh
 A<#:<33>:<34>:>
 A#<:<55>:<56>:>
 A<:<77>:<78> i<j>
 A<B<C>D #
 E<F<G>H #
 I<J>K> 
 L<<M>N 
 O<P>>Q  #
 R<<S>  T<<>

------------------------------
'A<:<11>:<12>:>'
'A<#:<33>:<34>:>'
'A#<:<55>:<56>:>'
'i<j>'
'E<F<G>H #\n I<J>K>'
'L<<M>N \n O<P>>Q'

----------- with \n -------------
'A<:<11>:<12>:>'
'A<#:<33>:<34>:>'
'A#<:<55>:<56>:>'
'i<j>'

------------- with # -----------
'A<:<11>:<12>:>'
'A#<:<55>:<56>:>'
'i<j>'
'L<<M>N \n O<P>>Q'

------ with ^# everywhere -------
'A<:<11>:<12>:>'
'i<j>'
'L<<M>N \n O<P>>Q'
于 2013-03-07T22:12:26.530 回答
0

我相信这些None值是由于()基于文档中的这一行的模式中存在 s :

如果在模式中使用捕获括号,则模式中所有组的文本也作为结果列表的一部分返回

在您的输入上使用正则表达式测试器也可能有助于可视化解析:http: //regexpal.com/?flags=g®ex=%28\S*%3C.*%3F%3E\S*%29|\s%2B&input =垃圾邮件%20bar%20ds%3Chai%20bye%3Esd%20baz%20eggs

于 2013-03-07T20:24:29.127 回答